diff --git a/.gitignore b/.gitignore index e65155d73..2cf37b67d 100755 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,4 @@ build_terminal/ generated_proto/ terminal.*/ BlockSettleHW/ledger/hidapi/* +GUI/QtWidgets/ui/* diff --git a/.gitmodules b/.gitmodules index 62ae3e27d..62f5480dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ -[submodule "Celer"] - path = Celer - url = ../Celer [submodule "common"] path = common url = ../common -[submodule "AuthAPI"] - path = AuthAPI - url = https://github.com/autheid/AuthAPI.git diff --git a/AuthAPI b/AuthAPI deleted file mode 160000 index 14a152087..000000000 --- a/AuthAPI +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14a1520870980fc8eec9455918edd96470144fdf diff --git a/BlockSettleApp/CMakeLists.txt b/BlockSettleApp/CMakeLists.txt index 248ccf0c1..315ab5303 100644 --- a/BlockSettleApp/CMakeLists.txt +++ b/BlockSettleApp/CMakeLists.txt @@ -47,12 +47,14 @@ ELSE () ENDIF () TARGET_LINK_LIBRARIES( ${BLOCKSETTLE_APP_NAME} + ${TERMINAL_CORE_NAME} + ${TERMINAL_GUI_QT_NAME} + ${TERMINAL_GUI_QTQUICK_NAME} ${BLOCKSETTLE_UI_LIBRARY_NAME} ${BS_NETWORK_LIB_NAME} ${CPP_WALLET_LIB_NAME} ${CRYPTO_LIB_NAME} ${BOTAN_LIB} - ${ZMQ_LIB} ${QRENCODE_LIB} ${QT_LINUX_LIBS} ${WS_LIB} @@ -60,6 +62,8 @@ TARGET_LINK_LIBRARIES( ${BLOCKSETTLE_APP_NAME} Qt5::Core Qt5::Widgets Qt5::Gui + Qt5::QSQLiteDriverPlugin + Qt5::Sql Qt5::Network Qt5::PrintSupport Qt5::Core diff --git a/BlockSettleApp/main.cpp b/BlockSettleApp/main.cpp index 4e8185ef6..ebc4e6579 100644 --- a/BlockSettleApp/main.cpp +++ b/BlockSettleApp/main.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -9,30 +9,34 @@ */ #include -#include #include #include -#include -#include -#include -#include +#include +#include #include #include -#include #include - #include - #include "ApplicationSettings.h" #include "BSErrorCode.h" #include "BSMessageBox.h" -#include "BSTerminalMainWindow.h" #include "BSTerminalSplashScreen.h" #include "EncryptionUtils.h" -#include "btc/ecc.h" - -#include "AppNap.h" +#include "Adapters/BlockchainAdapter.h" +#include "Adapters/WalletsAdapter.h" +#include "ApiAdapter.h" +#include "ApiJson.h" +#include "AssetsAdapter.h" +#include "BsServerAdapter.h" +#include "QtGuiAdapter.h" +#include "QtQuickAdapter.h" +#include "SettingsAdapter.h" +#include "SignerAdapter.h" +#include +#include + +//#include "AppNap.h" #ifdef USE_QWindowsIntegrationPlugin Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) @@ -49,16 +53,30 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin) Q_IMPORT_PLUGIN(QCupsPrinterSupportPlugin) #endif // USE_QXcbIntegrationPlugin -#ifdef STATIC_BUILD Q_IMPORT_PLUGIN(QSQLiteDriverPlugin) Q_IMPORT_PLUGIN(QICOPlugin) + +#ifdef STATIC_BUILD +#if defined (Q_OS_LINUX) +Q_IMPORT_PLUGIN(QtQuick2PrivateWidgetsPlugin) +#endif + +Q_IMPORT_PLUGIN(QtQuick2Plugin) +Q_IMPORT_PLUGIN(QtQuick2WindowPlugin) +Q_IMPORT_PLUGIN(QtQuickControls2Plugin) +Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin) +//Q_IMPORT_PLUGIN(QtQuickControls1Plugin) +Q_IMPORT_PLUGIN(QtQuickLayoutsPlugin) +Q_IMPORT_PLUGIN(QtQmlModelsPlugin) +Q_IMPORT_PLUGIN(QmlFolderListModelPlugin) +Q_IMPORT_PLUGIN(QmlSettingsPlugin) +//Q_IMPORT_PLUGIN(QtLabsPlatformPlugin) #endif // STATIC_BUILD Q_DECLARE_METATYPE(ArmorySettings) Q_DECLARE_METATYPE(AsyncClient::LedgerDelegate) Q_DECLARE_METATYPE(BinaryData) Q_DECLARE_METATYPE(bs::error::AuthAddressSubmitResult); -Q_DECLARE_METATYPE(CelerAPI::CelerMessageType); Q_DECLARE_METATYPE(SecureBinaryData) Q_DECLARE_METATYPE(std::shared_ptr>) Q_DECLARE_METATYPE(std::string) @@ -109,12 +127,11 @@ static void checkStyleSheet(QApplication &app) QFileInfo info = QFileInfo(QLatin1String(styleSheetFileName)); - static QDateTime lastTimestamp = info.lastModified(); + static auto lastTimestamp = info.lastModified(); if (lastTimestamp == info.lastModified()) { return; } - lastTimestamp = info.lastModified(); QFile stylesheetFile(styleSheetFileName); @@ -125,7 +142,7 @@ static void checkStyleSheet(QApplication &app) app.setStyleSheet(QString::fromLatin1(stylesheetFile.readAll())); } -QScreen *getDisplay(QPoint position) +static QScreen *getDisplay(QPoint position) { for (auto currentScreen : QGuiApplication::screens()) { if (currentScreen->availableGeometry().contains(position, false)) { @@ -136,151 +153,6 @@ QScreen *getDisplay(QPoint position) return QGuiApplication::primaryScreen(); } -static int runUnchecked(QApplication *app, const std::shared_ptr &settings - , BSTerminalSplashScreen &splashScreen, QLockFile &lockFile) -{ - BSTerminalMainWindow mainWindow(settings, splashScreen, lockFile); - -#if defined (Q_OS_MAC) - MacOsApp *macApp = (MacOsApp*)(app); - - QObject::connect(macApp, &MacOsApp::reactivateTerminal, &mainWindow, &BSTerminalMainWindow::onReactivate); -#endif - - if (!settings->get(ApplicationSettings::launchToTray)) { - mainWindow.loadPositionAndShow(); - } - - mainWindow.postSplashscreenActions(); - - bs::disableAppNap(); - - return app->exec(); -} - -static int runChecked(QApplication *app, const std::shared_ptr &settings - , BSTerminalSplashScreen &splashScreen, QLockFile &lockFile) -{ - try { - return runUnchecked(app, settings, splashScreen, lockFile); - } - catch (const std::exception &e) { - std::cerr << "Failed to start BlockSettle Terminal: " << e.what() << std::endl; - BSMessageBox(BSMessageBox::critical, app->tr("BlockSettle Terminal") - , app->tr("Unhandled exception detected: %1").arg(QLatin1String(e.what()))).exec(); - return EXIT_FAILURE; - } -} - -static int GuiApp(int &argc, char** argv) -{ - Q_INIT_RESOURCE(armory); - Q_INIT_RESOURCE(tradinghelp); - Q_INIT_RESOURCE(wallethelp); - - QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - -#if defined (Q_OS_MAC) - MacOsApp app(argc, argv); -#else - QApplication app(argc, argv); -#endif - - - QApplication::setQuitOnLastWindowClosed(false); - - QFileInfo localStyleSheetFile(QLatin1String("stylesheet.css")); - - QFile stylesheetFile(localStyleSheetFile.exists() - ? localStyleSheetFile.fileName() - : QLatin1String(":/STYLESHEET")); - - if (stylesheetFile.open(QFile::ReadOnly)) { - app.setStyleSheet(QString::fromLatin1(stylesheetFile.readAll())); - QPalette p = QApplication::palette(); - p.setColor(QPalette::Disabled, QPalette::Light, QColor(10,22,25)); - QApplication::setPalette(p); - } - -#ifndef NDEBUG - // Start monitoring to update stylesheet live when file is changed on the disk - QTimer timer; - QObject::connect(&timer, &QTimer::timeout, &app, [&app] { - checkStyleSheet(app); - }); - timer.start(100); -#endif - - QDirIterator it(QLatin1String(":/resources/Raleway/")); - while (it.hasNext()) { - QFontDatabase::addApplicationFont(it.next()); - } - - QString location = QStandardPaths::writableLocation(QStandardPaths::TempLocation); -#ifndef NDEBUG - QString userName = QDir::home().dirName(); - QString lockFilePath = location + QLatin1String("/blocksettle-") + userName + QLatin1String(".lock"); -#else - QString lockFilePath = location + QLatin1String("/blocksettle.lock"); -#endif - QLockFile lockFile(lockFilePath); - lockFile.setStaleLockTime(0); - - if (!lockFile.tryLock()) { - BSMessageBox box(BSMessageBox::info, app.tr("BlockSettle Terminal") - , app.tr("BlockSettle Terminal is already running") - , app.tr("Stop the other BlockSettle Terminal instance. If no other " \ - "instance is running, delete the lockfile (%1).").arg(lockFilePath)); - return box.exec(); - } - - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType>(); - qRegisterMetaType(); - qRegisterMetaType>>(); - qRegisterMetaType(); - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType(); - - // load settings - auto settings = std::make_shared(); - if (!settings->LoadApplicationSettings(app.arguments())) { - BSMessageBox errorMessage(BSMessageBox::critical, app.tr("Error") - , app.tr("Failed to parse command line arguments") - , settings->ErrorText()); - errorMessage.exec(); - return EXIT_FAILURE; - } - - QString logoIcon; - logoIcon = QLatin1String(":/SPLASH_LOGO"); - - QPixmap splashLogo(logoIcon); - const int splashScreenWidth = 400; - BSTerminalSplashScreen splashScreen(splashLogo.scaledToWidth(splashScreenWidth, Qt::SmoothTransformation)); - - auto mainGeometry = settings->get(ApplicationSettings::GUI_main_geometry); - auto currentDisplay = getDisplay(mainGeometry.center()); - auto splashGeometry = splashScreen.geometry(); - splashGeometry.moveCenter(currentDisplay->geometry().center()); - splashScreen.setGeometry(splashGeometry); - - app.processEvents(); - -#ifdef NDEBUG - return runChecked(&app, settings, splashScreen, lockFile); -#else - return runUnchecked(&app, settings, splashScreen, lockFile); -#endif -} - int main(int argc, char** argv) { srand(std::time(nullptr)); @@ -288,11 +160,72 @@ int main(int argc, char** argv) // Initialize libbtc, BIP 150, and BIP 151. 150 uses the proprietary "public" // Armory setting designed to allow the ArmoryDB server to not have to verify // clients. Prevents us from having to import tons of keys into the server. - btc_ecc_start(); + CryptoECDSA::setupContext(); startupBIP151CTX(); startupBIP150CTX(4); - return GuiApp(argc, argv); + QStringList args; + for (int i = 0; i < argc; ++i) { + args << QLatin1String(argv[i]); + } +#ifdef NDEBUG + try { +#endif //NDEBUG + const auto &settings = std::make_shared(QLatin1Literal("BlockSettle Terminal") + , QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QDir::separator() + ApplicationSettings::appSubDir()); + const auto &adSettings = std::make_shared(settings, args); + const auto &logMgr = adSettings->logManager(); + spdlog::set_default_logger(logMgr->logger()); + + bs::message::TerminalInprocBus inprocBus(logMgr->logger()); + inprocBus.addAdapter(adSettings); + + const auto &apiAdapter = std::make_shared(logMgr->logger("API")); + std::shared_ptr guiAdapter; + if (adSettings->guiMode() == "qtwidgets") { + guiAdapter = std::make_shared(logMgr->logger("ui")); + } + else if (adSettings->guiMode() == "qtquick") { + guiAdapter = std::make_shared(logMgr->logger("ui")); + } + else { + throw std::runtime_error("unknown GUI mode " + adSettings->guiMode()); + } + apiAdapter->add(guiAdapter); + apiAdapter->add(std::make_shared(logMgr->logger("json"))); + inprocBus.addAdapter(apiAdapter); + + const auto &signAdapter = std::make_shared(logMgr->logger()); + inprocBus.addAdapterWithQueue(signAdapter, "signer"); + + const auto& userBlockchain = bs::message::UserTerminal::create(bs::message::TerminalUsers::Blockchain); + const auto& userWallets = bs::message::UserTerminal::create(bs::message::TerminalUsers::Wallets); + //inprocBus.addAdapter(std::make_shared(logMgr->logger())); + inprocBus.addAdapterWithQueue(std::make_shared(logMgr->logger() + , userWallets, signAdapter->createClient(), userBlockchain), "wallets"); + inprocBus.addAdapter(std::make_shared(logMgr->logger("bscon"))); + //inprocBus.addAdapter(std::make_shared(logMgr->logger("match"))); + //inprocBus.addAdapter(std::make_shared(logMgr->logger("settl"))); + //inprocBus.addAdapter(std::make_shared(logMgr->logger("md"))); + //inprocBus.addAdapter(std::make_shared(logMgr->logger("mdh"))); + //inprocBus.addAdapter(std::make_shared(logMgr->logger("chat"))); + inprocBus.addAdapterWithQueue(std::make_shared(logMgr->logger() + , userBlockchain), /*"blkchain_conn"*/"signer"); + + if (!inprocBus.run(argc, argv)) { + logMgr->logger()->error("No runnable adapter found on main inproc bus"); + return EXIT_FAILURE; + } +#ifdef NDEBUG + } + catch (const std::exception &e) { + std::cerr << "Failed to start BlockSettle Terminal: " << e.what() << std::endl; + BSMessageBox(BSMessageBox::critical, QObject::tr("BlockSettle Terminal") + , QObject::tr("Unhandled exception detected: %1").arg(QLatin1String(e.what()))).exec(); + return EXIT_FAILURE; + } +#endif //NDEBUG } #include "main.moc" diff --git a/BlockSettleHW/CMakeLists.txt b/BlockSettleHW/CMakeLists.txt index 3a4c39b82..f6efd92cb 100644 --- a/BlockSettleHW/CMakeLists.txt +++ b/BlockSettleHW/CMakeLists.txt @@ -1,7 +1,7 @@ # # # *********************************************************************************** -# * Copyright (C) 2020 - 2020, BlockSettle AB +# * Copyright (C) 2020 - 2021, BlockSettle AB # * Distributed under the GNU Affero General Public License (AGPL v3) # * See LICENSE or http://www.gnu.org/licenses/agpl.html # * diff --git a/BlockSettleHW/hwcommonstructure.cpp b/BlockSettleHW/hwcommonstructure.cpp index d4682bee0..5243a6dfd 100644 --- a/BlockSettleHW/hwcommonstructure.cpp +++ b/BlockSettleHW/hwcommonstructure.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/hwcommonstructure.h b/BlockSettleHW/hwcommonstructure.h index 30bfb3ac4..6448d7ed4 100644 --- a/BlockSettleHW/hwcommonstructure.h +++ b/BlockSettleHW/hwcommonstructure.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/hwdeviceinterface.h b/BlockSettleHW/hwdeviceinterface.h index 8e2ae2825..5191b9c09 100644 --- a/BlockSettleHW/hwdeviceinterface.h +++ b/BlockSettleHW/hwdeviceinterface.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/hwdevicemanager.cpp b/BlockSettleHW/hwdevicemanager.cpp index 1b69ab0c6..88396ca11 100644 --- a/BlockSettleHW/hwdevicemanager.cpp +++ b/BlockSettleHW/hwdevicemanager.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -14,12 +14,11 @@ #include "ledger/ledgerClient.h" #include "ledger/ledgerDevice.h" #include "ConnectionManager.h" -#include "WalletManager.h" #include "Wallets/SyncWalletsManager.h" #include "Wallets/SyncHDWallet.h" -#include "ProtobufHeadlessUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" -using namespace ArmorySigner; +using namespace Armory::Signer; HwDeviceManager::HwDeviceManager(const std::shared_ptr& connectionManager, std::shared_ptr walletManager, bool testNet, QObject* parent /*= nullptr*/) diff --git a/BlockSettleHW/hwdevicemanager.h b/BlockSettleHW/hwdevicemanager.h index 42432c875..dc20a23a0 100644 --- a/BlockSettleHW/hwdevicemanager.h +++ b/BlockSettleHW/hwdevicemanager.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/hwdevicemodel.cpp b/BlockSettleHW/hwdevicemodel.cpp index 5ec783a84..f465caad0 100644 --- a/BlockSettleHW/hwdevicemodel.cpp +++ b/BlockSettleHW/hwdevicemodel.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/hwdevicemodel.h b/BlockSettleHW/hwdevicemodel.h index fcbcfbf74..d33510d9a 100644 --- a/BlockSettleHW/hwdevicemodel.h +++ b/BlockSettleHW/hwdevicemodel.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/ledger/ledgerClient.cpp b/BlockSettleHW/ledger/ledgerClient.cpp index a91812666..6ba0ddebc 100644 --- a/BlockSettleHW/ledger/ledgerClient.cpp +++ b/BlockSettleHW/ledger/ledgerClient.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/ledger/ledgerClient.h b/BlockSettleHW/ledger/ledgerClient.h index ec97d61eb..3b14238f2 100644 --- a/BlockSettleHW/ledger/ledgerClient.h +++ b/BlockSettleHW/ledger/ledgerClient.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/ledger/ledgerDevice.cpp b/BlockSettleHW/ledger/ledgerDevice.cpp index 9f6912caf..aa5214db3 100644 --- a/BlockSettleHW/ledger/ledgerDevice.cpp +++ b/BlockSettleHW/ledger/ledgerDevice.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -12,7 +12,7 @@ #include "ledger/ledgerDevice.h" #include "ledger/ledgerClient.h" #include "Assets.h" -#include "ProtobufHeadlessUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" #include "CoreWallet.h" #include "Wallets/SyncWalletsManager.h" #include "Wallets/SyncHDWallet.h" @@ -539,7 +539,7 @@ BIP32_Node LedgerCommandThread::getPublicKeyApdu(bs::hd::Path&& derivationPath, bool result = pubKey.parseFromResponse(response); auto data = SecureBinaryData::fromString(pubKey.pubKey_.toStdString()); - Asset_PublicKey pubKeyAsset(data); + Armory::Assets::Asset_PublicKey pubKeyAsset(data); SecureBinaryData chainCode = SecureBinaryData::fromString(pubKey.chainCode_.toStdString()); uint32_t fingerprint = 0; diff --git a/BlockSettleHW/ledger/ledgerDevice.h b/BlockSettleHW/ledger/ledgerDevice.h index d8316fd18..914238402 100644 --- a/BlockSettleHW/ledger/ledgerDevice.h +++ b/BlockSettleHW/ledger/ledgerDevice.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/ledger/ledgerStructure.cpp b/BlockSettleHW/ledger/ledgerStructure.cpp index f30bc8303..ca1963ba2 100644 --- a/BlockSettleHW/ledger/ledgerStructure.cpp +++ b/BlockSettleHW/ledger/ledgerStructure.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/ledger/ledgerStructure.h b/BlockSettleHW/ledger/ledgerStructure.h index de3e27e71..acaafac3c 100644 --- a/BlockSettleHW/ledger/ledgerStructure.h +++ b/BlockSettleHW/ledger/ledgerStructure.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/trezor/trezorClient.cpp b/BlockSettleHW/trezor/trezorClient.cpp index cd7a4dfbb..c1a440089 100644 --- a/BlockSettleHW/trezor/trezorClient.cpp +++ b/BlockSettleHW/trezor/trezorClient.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -306,15 +306,17 @@ void TrezorClient::post(QByteArray&& urlMethod, std::functionGetNAM()->post(request, input); - auto connection = connect(reply, &QNetworkReply::finished, this, [cbCopy = cb, repCopy = reply, sender = QPointer(this)]{ - if (!sender) { - return; // TREZOR client already destroyed - } + QNetworkReply *reply = QNetworkAccessManager().post(request, input); + auto connection = connect(reply, &QNetworkReply::finished, this + , [cbCopy = cb, repCopy = reply, sender = QPointer(this)] + { + if (!sender) { + return; // TREZOR client already destroyed + } - cbCopy(repCopy); - repCopy->deleteLater(); - }); + cbCopy(repCopy); + repCopy->deleteLater(); + }); // Timeout if (timeout) { @@ -325,7 +327,6 @@ void TrezorClient::post(QByteArray&& urlMethod, std::functionabort(); }); } - } void TrezorClient::cleanDeviceData() diff --git a/BlockSettleHW/trezor/trezorClient.h b/BlockSettleHW/trezor/trezorClient.h index 0cda3130b..5f3fac1f6 100644 --- a/BlockSettleHW/trezor/trezorClient.h +++ b/BlockSettleHW/trezor/trezorClient.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/trezor/trezorDevice.cpp b/BlockSettleHW/trezor/trezorDevice.cpp index 9edd7a458..1f580e25a 100644 --- a/BlockSettleHW/trezor/trezorDevice.cpp +++ b/BlockSettleHW/trezor/trezorDevice.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -12,7 +12,7 @@ #include "trezorClient.h" #include "ConnectionManager.h" #include "headless.pb.h" -#include "ProtobufHeadlessUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" #include "CoreWallet.h" #include "Wallets/SyncWalletsManager.h" #include "Wallets/SyncHDWallet.h" diff --git a/BlockSettleHW/trezor/trezorDevice.h b/BlockSettleHW/trezor/trezorDevice.h index d4c630b1c..3dcc6d838 100644 --- a/BlockSettleHW/trezor/trezorDevice.h +++ b/BlockSettleHW/trezor/trezorDevice.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleHW/trezor/trezorStructure.h b/BlockSettleHW/trezor/trezorStructure.h index 1d5b887ac..0b71a300d 100644 --- a/BlockSettleHW/trezor/trezorStructure.h +++ b/BlockSettleHW/trezor/trezorStructure.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleSigner/Bip39EntryValidator.cpp b/BlockSettleSigner/Bip39EntryValidator.cpp index de594a44b..f8e9c1008 100644 --- a/BlockSettleSigner/Bip39EntryValidator.cpp +++ b/BlockSettleSigner/Bip39EntryValidator.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -9,7 +9,6 @@ */ #include "Bip39EntryValidator.h" -#include "Bip39.h" #include "QmlFactory.h" Bip39EntryValidator::Bip39EntryValidator(QObject *parent) @@ -33,10 +32,11 @@ QValidator::State Bip39EntryValidator::validate(QString &input, int &pos) const return State::Intermediate; } +#if 0 //FIXME: use another BIP39 implementation if (!validateMnemonic(input.toStdString(), dictionaries_)) { return State::Invalid; } - +#endif return State::Acceptable; } diff --git a/BlockSettleSigner/Bip39EntryValidator.h b/BlockSettleSigner/Bip39EntryValidator.h index 0d056fd1c..1a837018a 100644 --- a/BlockSettleSigner/Bip39EntryValidator.h +++ b/BlockSettleSigner/Bip39EntryValidator.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleSigner/CMakeLists.txt b/BlockSettleSigner/CMakeLists.txt index 154f00109..a0b2e54e9 100644 --- a/BlockSettleSigner/CMakeLists.txt +++ b/BlockSettleSigner/CMakeLists.txt @@ -80,7 +80,6 @@ TARGET_LINK_LIBRARIES(${SIGNER_APP_NAME} ${CPP_WALLET_LIB_NAME} ${CRYPTO_LIB_NAME} ${BOTAN_LIB} - ${ZMQ_LIB} ${WS_LIB} ${QRENCODE_LIB} ${QT_LINUX_LIBS} diff --git a/BlockSettleSigner/EasyEncValidator.cpp b/BlockSettleSigner/EasyEncValidator.cpp index b4ab173cb..3e3907cdf 100644 --- a/BlockSettleSigner/EasyEncValidator.cpp +++ b/BlockSettleSigner/EasyEncValidator.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleSigner/EasyEncValidator.h b/BlockSettleSigner/EasyEncValidator.h index 06f8e7adc..05cfdd1ae 100644 --- a/BlockSettleSigner/EasyEncValidator.h +++ b/BlockSettleSigner/EasyEncValidator.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -21,7 +21,6 @@ class EasyEncValidator : public QValidator { - Q_OBJECT Q_PROPERTY(QString statusMsg READ getStatusMsg NOTIFY statusMsgChanged) Q_PROPERTY(QString name READ getName WRITE setName) @@ -35,11 +34,11 @@ class EasyEncValidator : public QValidator }; //contructor used by the QmlEngine - explicit EasyEncValidator(QObject *parent = nullptr) : + EasyEncValidator(QObject *parent = nullptr) : QValidator(parent), wordSize_(4), numWords_(9), - hasChecksum_(true) + hasChecksum_(false) {} State validate(QString &input, int &pos) const override; diff --git a/BlockSettleSigner/HeadlessApp.cpp b/BlockSettleSigner/HeadlessApp.cpp index fa418623e..1d71fcb78 100644 --- a/BlockSettleSigner/HeadlessApp.cpp +++ b/BlockSettleSigner/HeadlessApp.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -25,7 +25,7 @@ #include "DispatchQueue.h" #include "GenoaStreamServerConnection.h" #include "HeadlessApp.h" -#include "HeadlessContainerListener.h" +#include "Wallets/HeadlessContainerListener.h" #include "Settings/HeadlessSettings.h" #include "SignerAdapterListener.h" #include "SignerVersion.h" @@ -36,6 +36,7 @@ #include "bs_signer.pb.h" using namespace bs::error; +using namespace Blocksettle::Communication; HeadlessAppObj::HeadlessAppObj(const std::shared_ptr &logger , const std::shared_ptr ¶ms, const std::shared_ptr &queue) @@ -321,13 +322,7 @@ void HeadlessAppObj::startTerminalsProcessing() if (!result) { logger_->error("Failed to bind to {}:{}" , settings_->listenAddress(), settings_->listenPort()); - - // Abort only if litegui used, fullgui should just show error message instead - if (settings_->runMode() == bs::signer::RunMode::litegui) { - throw std::runtime_error("failed to bind listening socket"); - } } - signerBindStatus_ = result ? bs::signer::BindStatus::Succeed : bs::signer::BindStatus::Failed; } @@ -480,13 +475,6 @@ void HeadlessAppObj::passwordReceived(const std::string &walletId, ErrorCode res } } -void HeadlessAppObj::windowVisibilityChanged(bool visible) -{ - if (terminalListener_ && settings_->runMode() == bs::signer::RunMode::litegui) { - terminalListener_->windowVisibilityChanged(visible); - } -} - void HeadlessAppObj::close() { queue_->quit(); diff --git a/BlockSettleSigner/HeadlessApp.h b/BlockSettleSigner/HeadlessApp.h index 4858d7db5..f070d1d57 100644 --- a/BlockSettleSigner/HeadlessApp.h +++ b/BlockSettleSigner/HeadlessApp.h @@ -15,7 +15,7 @@ #include #include -#include "SignerDefs.h" +#include "Wallets/SignerDefs.h" #include "BSErrorCode.h" namespace spdlog { @@ -61,7 +61,6 @@ class HeadlessAppObj void reloadWallets(bool notifyGUI, const std::function & = nullptr); void setLimits(bs::signer::Limits); void passwordReceived(const std::string &walletId, bs::error::ErrorCode result, const SecureBinaryData &); - void windowVisibilityChanged(bool visible); bs::error::ErrorCode activateAutoSign(const std::string &walletId, bool activate, const SecureBinaryData& password); diff --git a/BlockSettleSigner/PdfBackupQmlPrinter.h b/BlockSettleSigner/PdfBackupQmlPrinter.h index 252d47a4b..9c9429022 100644 --- a/BlockSettleSigner/PdfBackupQmlPrinter.h +++ b/BlockSettleSigner/PdfBackupQmlPrinter.h @@ -13,7 +13,7 @@ #include -#include "QSeed.h" +#include "Wallets/QSeed.h" #include "PaperBackupWriter.h" #include diff --git a/BlockSettleSigner/QMLStatusUpdater.h b/BlockSettleSigner/QMLStatusUpdater.h index 30333862d..f4b5e76e8 100644 --- a/BlockSettleSigner/QMLStatusUpdater.h +++ b/BlockSettleSigner/QMLStatusUpdater.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include "Settings/SignerSettings.h" namespace spdlog { diff --git a/BlockSettleSigner/QmlBridge.cpp b/BlockSettleSigner/QmlBridge.cpp index db1a1b07c..9747ff2f2 100644 --- a/BlockSettleSigner/QmlBridge.cpp +++ b/BlockSettleSigner/QmlBridge.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -9,7 +9,7 @@ */ #include "QmlBridge.h" -#include "SignerUiDefs.h" +#include "Wallets/SignerUiDefs.h" #include diff --git a/BlockSettleSigner/QmlBridge.h b/BlockSettleSigner/QmlBridge.h index ae6fbb04c..57a0efd40 100644 --- a/BlockSettleSigner/QmlBridge.h +++ b/BlockSettleSigner/QmlBridge.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleSigner/QmlCallbackImpl.h b/BlockSettleSigner/QmlCallbackImpl.h index be32bf9eb..50b3751ec 100644 --- a/BlockSettleSigner/QmlCallbackImpl.h +++ b/BlockSettleSigner/QmlCallbackImpl.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleSigner/QmlFactory.cpp b/BlockSettleSigner/QmlFactory.cpp index 8ea95bb17..84ce9fbc5 100644 --- a/BlockSettleSigner/QmlFactory.cpp +++ b/BlockSettleSigner/QmlFactory.cpp @@ -47,9 +47,13 @@ void QmlFactory::setWalletsManager(const std::shared_ptrdebug("[QmlFactory] signing {}", walletInfo->walletId().toStdString()); - AuthSignWalletObject *authObject = new AuthSignWalletObject(logger_, settings_, connectionManager_); - authObject->connectToServer(); - authObject->signWallet(requestType, walletInfo, authEidMessage, expiration, timestamp); - QQmlEngine::setObjectOwnership(authObject, QQmlEngine::JavaScriptOwnership); - return authObject; -} - -AuthSignWalletObject *QmlFactory::createActivateEidObject(const QString &walletId, const QString &authEidMessage, QJSValue callback) -{ - AuthSignWalletObject *authObject = new AuthSignWalletObject(logger_, settings_, connectionManager_); - authObject->connectToServer(); - authObject->activateWallet(walletId, authEidMessage, callback); - QQmlEngine::setObjectOwnership(authObject, QQmlEngine::JavaScriptOwnership); - return authObject; -} - -AuthSignWalletObject *QmlFactory::createAddEidObject(WalletInfo *walletInfo, const QString &authEidMessage, QJSValue callback) -{ - AuthSignWalletObject *authObject = new AuthSignWalletObject(logger_, settings_, connectionManager_); - authObject->connectToServer(); - authObject->addDevice(walletInfo->walletId(), authEidMessage, callback, walletInfo->email()); - QQmlEngine::setObjectOwnership(authObject, QQmlEngine::JavaScriptOwnership); - return authObject; -} - -AuthSignWalletObject *QmlFactory::createRemoveEidObject(int index, WalletInfo *walletInfo, const QString &authEidMessage) -{ - logger_->debug("[QmlFactory] remove device for {}, device index: {}", walletInfo->walletId().toStdString(), index); - AuthSignWalletObject *authObject = new AuthSignWalletObject(logger_, settings_, connectionManager_); - authObject->connectToServer(); - authObject->removeDevice(index, walletInfo, authEidMessage); - QQmlEngine::setObjectOwnership(authObject, QQmlEngine::JavaScriptOwnership); - return authObject; -} - void QmlFactory::setClipboard(const QString &text) const { QApplication::clipboard()->setText(text); diff --git a/BlockSettleSigner/QmlFactory.h b/BlockSettleSigner/QmlFactory.h index 111d2a309..2712fe4f3 100644 --- a/BlockSettleSigner/QmlFactory.h +++ b/BlockSettleSigner/QmlFactory.h @@ -14,12 +14,10 @@ #include #include #include - -#include "QSeed.h" -#include "QPasswordData.h" -#include "AuthProxy.h" - #include "BSErrorCode.h" +#include "Wallets/QSeed.h" +#include "Wallets/QPasswordData.h" +#include "Wallets/QWalletInfo.h" #include "bs_signer.pb.h" @@ -29,6 +27,8 @@ namespace bs { class WalletsManager; } } +class ApplicationSettings; +class ConnectionManager; class ControlPasswordStatus : public QObject { @@ -55,8 +55,8 @@ class QmlFactory : public QObject Q_PROPERTY(QString headlessPubKey READ headlessPubKey WRITE setHeadlessPubKey NOTIFY headlessPubKeyChanged) public: - QmlFactory(const std::shared_ptr &settings - , const std::shared_ptr &connectionManager + QmlFactory(const std::shared_ptr & + , const std::shared_ptr & , const std::shared_ptr &logger , QObject *parent = nullptr); ~QmlFactory() override = default; @@ -123,25 +123,6 @@ class QmlFactory : public QObject Q_INVOKABLE bs::hd::WalletInfo *createWalletInfo(int index) const; Q_INVOKABLE bs::hd::WalletInfo *createWalletInfoFromDigitalBackup(const QString &filename) const; - // Auth - // used for signing - Q_INVOKABLE AuthSignWalletObject *createAutheIDSignObject(AutheIDClient::RequestType requestType - , bs::hd::WalletInfo *walletInfo, const QString &authEidMessage - , int expiration = AutheIDClient::kDefaultExpiration, int timestamp = 0); - - // used for add eID - Q_INVOKABLE AuthSignWalletObject *createActivateEidObject(const QString &walletId - , const QString &authEidMessage, QJSValue callback); - - // used for add new eID device - Q_INVOKABLE AuthSignWalletObject *createAddEidObject(bs::hd::WalletInfo *walletInfo - , const QString &authEidMessage, QJSValue callback); - - // used for remove eID device - // index: is encKeys index which should be deleted - Q_INVOKABLE AuthSignWalletObject *createRemoveEidObject(int index - , bs::hd::WalletInfo *walletInfo, const QString &authEidMessage); - QString headlessPubKey() const; // service functions diff --git a/BlockSettleSigner/SignerAdapter.cpp b/BlockSettleSigner/SignerAdapter.cpp index ba59f0f4f..deb90af78 100644 --- a/BlockSettleSigner/SignerAdapter.cpp +++ b/BlockSettleSigner/SignerAdapter.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -14,7 +14,7 @@ #include #include "Bip15xDataConnection.h" -#include "SignContainer.h" +#include "Wallets/SignContainer.h" #include "SignerAdapterContainer.h" #include "SignerInterfaceListener.h" #include "SystemFileUtils.h" @@ -44,9 +44,7 @@ SignerAdapter::SignerAdapter(const std::shared_ptr &logger , const std::shared_ptr &qmlBridge , const NetworkType netType, int signerPort , std::shared_ptr adapterConn) - : QObject(nullptr) - ,logger_(logger) - , netType_(netType) + : QtHCT(nullptr), logger_(logger), netType_(netType) , qmlBridge_(qmlBridge) { listener_ = std::make_shared(logger, qmlBridge_, adapterConn, this); diff --git a/BlockSettleSigner/SignerAdapter.h b/BlockSettleSigner/SignerAdapter.h index 157912e47..74aaba156 100644 --- a/BlockSettleSigner/SignerAdapter.h +++ b/BlockSettleSigner/SignerAdapter.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -16,8 +16,9 @@ #include "CoreWallet.h" #include "QmlBridge.h" #include "QmlFactory.h" -#include "QPasswordData.h" -#include "SignerDefs.h" +#include "Wallets/QPasswordData.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/SignerDefs.h" #include "bs_signer.pb.h" @@ -45,7 +46,7 @@ class SignAdapterContainer; class SignerInterfaceListener; class DataConnection; -class SignerAdapter : public QObject +class SignerAdapter : public QtHCT { Q_OBJECT friend class SignerInterfaceListener; diff --git a/BlockSettleSigner/SignerAdapterContainer.cpp b/BlockSettleSigner/SignerAdapterContainer.cpp index faa382a44..63deacbc3 100644 --- a/BlockSettleSigner/SignerAdapterContainer.cpp +++ b/BlockSettleSigner/SignerAdapterContainer.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,11 +13,10 @@ #include #include #include -#include "CelerClientConnection.h" #include "DataConnection.h" #include "DataConnectionListener.h" #include "HeadlessApp.h" -#include "ProtobufHeadlessUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" #include "SignerInterfaceListener.h" #include "Wallets/SyncWalletsManager.h" @@ -48,7 +47,7 @@ void SignAdapterContainer::syncWalletInfo(const std::function #include "CoreWallet.h" #include "SignerAdapter.h" -#include "WalletSignerContainer.h" +#include "Wallets/WalletSignerContainer.h" namespace spdlog { class logger; } -class SignAdapterContainer : public WalletSignerContainer +class SignAdapterContainer : public WalletSignerContainer, public SignerCallbackTarget { public: SignAdapterContainer(const std::shared_ptr &logger , const std::shared_ptr &lsn) - : WalletSignerContainer(logger, OpMode::LocalInproc), listener_(lsn) + : WalletSignerContainer(logger, this, OpMode::LocalInproc), listener_(lsn) {} ~SignAdapterContainer() noexcept override = default; @@ -34,55 +34,24 @@ class SignAdapterContainer : public WalletSignerContainer bs::signer::RequestId signTXRequest(const bs::core::wallet::TXSignRequest & , const SecureBinaryData &password); - bs::signer::RequestId signTXRequest(const bs::core::wallet::TXSignRequest & - , TXSignMode = TXSignMode::Full, bool = false) override - { return 0; } - - void createSettlementWallet(const bs::Address & - , const std::function &) override {} - void setSettlementID(const std::string &, const SecureBinaryData & - , const std::function &) override {} - void getSettlementPayinAddress(const std::string &, - const bs::core::wallet::SettlementData &, const std::function &) override {} + void signTXRequest(const bs::core::wallet::TXSignRequest& + , const std::function& + , TXSignMode mode = TXSignMode::Full, bool keepDuplicatedRecipients = false) override {} + void getRootPubkey(const std::string& , const std::function &) override {} - bs::signer::RequestId signSettlementTXRequest(const bs::core::wallet::TXSignRequest & - , const bs::sync::PasswordDialogData & - , TXSignMode - , bool - , const std::function &) override {return 0; } - - bs::signer::RequestId signSettlementPartialTXRequest(const bs::core::wallet::TXSignRequest & - , const bs::sync::PasswordDialogData & - , const std::function & ) override { return 0; } - - bs::signer::RequestId signSettlementPayoutTXRequest(const bs::core::wallet::TXSignRequest & - , const bs::core::wallet::SettlementData &, const bs::sync::PasswordDialogData & - , const std::function &) override { return 0; } - - bs::signer::RequestId signAuthRevocation(const std::string &walletId, const bs::Address &authAddr - , const UTXO &, const bs::Address &bsAddr, const SignTxCb &cb = nullptr) override { return 0; } - bs::signer::RequestId resolvePublicSpenders(const bs::core::wallet::TXSignRequest & , const SignerStateCb &) override { return 0; } bs::signer::RequestId updateDialogData(const bs::sync::PasswordDialogData &, uint32_t = 0) override { return 0; } bs::signer::RequestId CancelSignTx(const BinaryData &tx) override { return 0; } - bs::signer::RequestId setUserId(const BinaryData &, const std::string &) override { return 0; } - bs::signer::RequestId syncCCNames(const std::vector &) override { return 0; } - bool createHDLeaf(const std::string&, const bs::hd::Path& , const std::vector& = {} , bs::sync::PasswordDialogData = {}, const CreateHDLeafCb & = nullptr) override { return false; } - bool enableTradingInHDWallet(const std::string &, const BinaryData & - , bs::sync::PasswordDialogData = {}, const UpdateWalletStructureCB& = nullptr) override { return false; } - - bool promoteWalletToPrimary(const std::string& rootWalletId - , bs::sync::PasswordDialogData dialogData = {}, const UpdateWalletStructureCB& cb = nullptr) override { return false; } - bs::signer::RequestId DeleteHDRoot(const std::string &rootWalletId) override; bs::signer::RequestId DeleteHDLeaf(const std::string &) override { return 0; } bs::signer::RequestId GetInfo(const std::string &) override { return 0; } @@ -99,15 +68,7 @@ class SignAdapterContainer : public WalletSignerContainer void extendAddressChain(const std::string&, unsigned, bool, const std::function> &)> &) override {} void syncNewAddresses(const std::string &, const std::vector & - , const std::function> &)> &, bool = true) override {} - void getChatNode(const std::string &walletID, const std::function &) override {} - void setSettlAuthAddr(const std::string &walletId, const BinaryData &, const bs::Address &addr) override {} - void getSettlAuthAddr(const std::string &walletId, const BinaryData & - , const std::function &) override {} - void setSettlCP(const std::string &walletId, const BinaryData &payinHash, const BinaryData &settlId - , const BinaryData &cpPubKey) override {} - void getSettlCP(const std::string &walletId, const BinaryData &payinHash - , const std::function &) override {} + , const std::function> &)> &) override {} bool isWalletOffline(const std::string &id) const override { return (woWallets_.find(id) != woWallets_.end()); } diff --git a/BlockSettleSigner/SignerAdapterListener.cpp b/BlockSettleSigner/SignerAdapterListener.cpp index 90a708682..4e4f7442a 100644 --- a/BlockSettleSigner/SignerAdapterListener.cpp +++ b/BlockSettleSigner/SignerAdapterListener.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -17,15 +17,18 @@ #include "CoreWalletsManager.h" #include "DispatchQueue.h" #include "HeadlessApp.h" -#include "HeadlessContainerListener.h" -#include "OfflineSigner.h" -#include "ProtobufHeadlessUtils.h" +#include "Wallets/HeadlessContainerListener.h" +#include "Wallets/OfflineSigner.h" +#include "Wallets/ProtobufHeadlessUtils.h" #include "ScopeGuard.h" #include "ServerConnection.h" #include "Settings/HeadlessSettings.h" #include "StringUtils.h" #include "SystemFileUtils.h" +#include "bs_signer.pb.h" +#include "headless.pb.h" + using namespace Blocksettle::Communication; class HeadlessContainerCallbacksImpl : public HeadlessContainerCallbacks @@ -55,11 +58,6 @@ class HeadlessContainerCallbacksImpl : public HeadlessContainerCallbacks signer::ClientDisconnected evt; evt.set_client_id(clientId); owner_->sendData(signer::PeerDisconnectedType, evt.SerializeAsString()); - - if (owner_->settings_->runMode() == bs::signer::RunMode::litegui) { - owner_->logger_->info("Quit because terminal disconnected unexpectedly and litegui used"); - owner_->queue_->quit(); - } } void ccNamesReceived(bool result) override @@ -123,7 +121,7 @@ class HeadlessContainerCallbacksImpl : public HeadlessContainerCallbacks signer::DecryptWalletRequest request; request.set_dialogtype(dialogType); *(request.mutable_signtxrequest()) = bs::signer::coreTxRequestToPb(txReq); - *(request.mutable_passworddialogdata()) = dialogData; + *(request.mutable_passworddialogdata()) = dialogData.data(); owner_->sendData(signer::DecryptWalletRequestType, request.SerializeAsString()); } @@ -131,7 +129,7 @@ class HeadlessContainerCallbacksImpl : public HeadlessContainerCallbacks void updateDialogData(const Internal::PasswordDialogDataWrapper &dialogData) override { headless::UpdateDialogDataRequest request; - *request.mutable_passworddialogdata() = dialogData; + *request.mutable_passworddialogdata() = dialogData.data(); owner_->sendData(signer::UpdateDialogDataType, request.SerializeAsString()); } @@ -255,9 +253,6 @@ void SignerAdapterListener::processData(const std::string &clientId, const std:: case signer::ChangeControlPasswordType: rc = onChangeControlPassword(packet.data(), packet.id()); break; - case signer::WindowStatusType: - rc = onWindowsStatus(packet.data(), packet.id()); - break; case signer::VerifyOfflineTxRequestType: rc = onVerifyOfflineTx(packet.data(), packet.id()); break; @@ -298,7 +293,7 @@ void SignerAdapterListener::sendStatusUpdate() callbacks_->ccNamesReceived(!walletsMgr_->ccLeaves().empty()); } -void SignerAdapterListener::sendControlPasswordStatusUpdate(signer::ControlPasswordStatus status) +void SignerAdapterListener::sendControlPasswordStatusUpdate(const signer::ControlPasswordStatus &status) { signer::UpdateControlPasswordStatus evt; evt.set_controlpasswordstatus(status); @@ -410,7 +405,7 @@ bool SignerAdapterListener::onSyncHDWallet(const std::string &data, bs::signer:: throw std::runtime_error("unexpected leaf type"); } const auto rootAsset = settlLeaf->getRootAsset(); - const auto rootSingle = std::dynamic_pointer_cast(rootAsset); + const auto rootSingle = std::dynamic_pointer_cast(rootAsset); if (rootSingle == nullptr) { throw std::runtime_error("invalid root asset"); } @@ -508,12 +503,12 @@ bool SignerAdapterListener::onGetDecryptedNode(const std::string &data, bs::sign seedStr = seed.seed().toBinStr(); privKeyStr = seed.toXpriv().toBinStr(); } - catch (const WalletException &e) { + catch (const Armory::Wallets::WalletException &e) { logger_->error("[SignerAdapterListener::onGetDecryptedNode] failed to decrypt wallet with id {}: {}" , request.wallet_id(), e.what()); return false; } - catch (const DecryptedDataContainerException &e) { + catch (const Armory::Wallets::Encryption::DecryptedDataContainerException &e) { logger_->error("[SignerAdapterListener::onGetDecryptedNode] wallet {} decryption failure: {}" , request.wallet_id(), e.what()); return false; @@ -586,17 +581,6 @@ bool SignerAdapterListener::onChangeControlPassword(const std::string &data, bs: return true; } -bool SignerAdapterListener::onWindowsStatus(const std::string &data, bs::signer::RequestId) -{ - headless::WindowStatus msg; - if (!msg.ParseFromString(data)) { - logger_->error("[SignerAdapterListener::{}] failed to parse request", __func__); - return false; - } - app_->windowVisibilityChanged(msg.visible()); - return true; -} - bool SignerAdapterListener::onVerifyOfflineTx(const std::string &data, bs::signer::RequestId reqId) { signer::VerifyOfflineTxRequest request; @@ -958,7 +942,7 @@ void SignerAdapterListener::onStarted() void SignerAdapterListener::shutdownIfNeeded() { - if (settings_->runMode() == bs::signer::RunMode::litegui && app_) { + if (app_) { logger_->info("terminal disconnect detected, shutdown..."); app_->close(); } diff --git a/BlockSettleSigner/SignerAdapterListener.h b/BlockSettleSigner/SignerAdapterListener.h index f13392dd3..1488f50e3 100644 --- a/BlockSettleSigner/SignerAdapterListener.h +++ b/BlockSettleSigner/SignerAdapterListener.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,16 +13,22 @@ #include #include "CoreWallet.h" -#include "SignerDefs.h" +#include "Wallets/SignerDefs.h" #include "ServerConnectionListener.h" #include "BSErrorCode.h" -#include "bs_signer.pb.h" -#include "headless.pb.h" namespace spdlog { class logger; } +namespace Blocksettle { + namespace Communication { + namespace signer { + enum ControlPasswordStatus : int; + enum PacketType : int; + } + } +} namespace bs { namespace core { namespace hd { @@ -34,6 +40,13 @@ namespace bs { class TransportBIP15xServer; } } +namespace Blocksettle { + namespace Communication { + namespace signer { + enum ControlPasswordStatus : int; + } + } +} class DispatchQueue; class HeadlessAppObj; class HeadlessContainerCallbacks; @@ -41,6 +54,7 @@ class HeadlessContainerCallbacksImpl; class HeadlessSettings; class ServerConnection; + class SignerAdapterListener : public ServerConnectionListener { public: @@ -54,7 +68,7 @@ class SignerAdapterListener : public ServerConnectionListener // Sent to GUI status update message void sendStatusUpdate(); - void sendControlPasswordStatusUpdate(signer::ControlPasswordStatus status); + void sendControlPasswordStatusUpdate(const Blocksettle::Communication::signer::ControlPasswordStatus &); void resetConnection(); @@ -95,7 +109,6 @@ class SignerAdapterListener : public ServerConnectionListener bool onSyncSettings(const std::string &data); bool onControlPasswordReceived(const std::string &data); bool onChangeControlPassword(const std::string &data, bs::signer::RequestId); - bool onWindowsStatus(const std::string &data, bs::signer::RequestId); bool onVerifyOfflineTx(const std::string &data, bs::signer::RequestId); void shutdownIfNeeded(); diff --git a/BlockSettleSigner/SignerInterfaceListener.cpp b/BlockSettleSigner/SignerInterfaceListener.cpp index 7e5046bd7..2fd018834 100644 --- a/BlockSettleSigner/SignerInterfaceListener.cpp +++ b/BlockSettleSigner/SignerInterfaceListener.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -16,14 +16,12 @@ #include #include #include -#include "CelerClientConnection.h" #include "DataConnection.h" #include "HeadlessApp.h" #include "Wallets/SyncWalletsManager.h" #include "ZmqContext.h" #include "SignerInterfaceListener.h" -#include "SignerAdapterContainer.h" #include using namespace bs::sync; @@ -421,15 +419,15 @@ void SignerInterfaceListener::onSyncHDWallet(const std::string &data, bs::signer return; } bs::sync::HDWalletData result; - for (int i = 0; i < response.groups_size(); ++i) { - const auto group = response.groups(i); + for (const auto &group : response.groups()) { std::vector leaves; - for (int j = 0; j < group.leaves_size(); ++j) { - const auto leaf = group.leaves(j); - leaves.push_back({ leaf.id(), bs::hd::Path::fromString(leaf.path()) - , false, BinaryData::fromString(leaf.extra_data()) }); + for (const auto &leaf : group.leaves()) { + leaves.push_back({ {leaf.id()}, bs::hd::Path::fromString(leaf.path()) + , std::string{}, std::string{}, false + , BinaryData::fromString(leaf.extra_data()) }); } - result.groups.push_back({ static_cast(group.type()), leaves }); + result.groups.push_back({ static_cast(group.type()) + , std::string{}, std::string{}, leaves }); } itCb->second(result); cbHDWalletData_.erase(itCb); diff --git a/BlockSettleSigner/SignerInterfaceListener.h b/BlockSettleSigner/SignerInterfaceListener.h index 61452b92d..9bcdfab76 100644 --- a/BlockSettleSigner/SignerInterfaceListener.h +++ b/BlockSettleSigner/SignerInterfaceListener.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,7 +13,7 @@ #include #include "DataConnectionListener.h" -#include "SignerUiDefs.h" +#include "Wallets/SignerUiDefs.h" #include "TXInfo.h" #include "bs_signer.pb.h" diff --git a/BlockSettleSigner/TXInfo.cpp b/BlockSettleSigner/TXInfo.cpp index 899d7897d..226a83ed6 100644 --- a/BlockSettleSigner/TXInfo.cpp +++ b/BlockSettleSigner/TXInfo.cpp @@ -11,12 +11,12 @@ #include "TXInfo.h" #include "CheckRecipSigner.h" -#include "OfflineSigner.h" +#include "Wallets/OfflineSigner.h" #include "TxClasses.h" #include "ScriptRecipient.h" #include "Wallets/SyncHDWallet.h" -#include "QWalletInfo.h" +#include "Wallets/QWalletInfo.h" #include #include @@ -99,22 +99,6 @@ double TXInfo::outputAmountFull() const return txReq_.armorySigner_.getTotalOutputsValue() / BTCNumericTypes::BalanceDivider; } -double TXInfo::amountCCReceived(const QString &cc) const -{ - ContainsAddressCb &containsCCAddressCb = [this, cc](const bs::Address &address){ - const auto &wallet = walletsMgr_->getCCWallet(cc.toStdString()); - return wallet->containsAddress(address); - }; - - return txReq_.amountReceived(containsCCAddressCb) / BTCNumericTypes::BalanceDivider; -} - -double TXInfo::amountCCSent() const -{ - return txReq_.totalSpent(containsAnyOurCCAddressCb_) / BTCNumericTypes::BalanceDivider; - -} - double TXInfo::amountXBTReceived() const { // calculate received amount from counterparty outputs @@ -197,7 +181,7 @@ QStringList TXInfo::counterPartyRecipients() const // Get recipients not listed in our wallets // Usable for settlement tx dialog - std::vector> recipientsList; + std::vector> recipientsList; recipientsList = txReq_.getRecipients(containsCounterPartyAddressCb_); QStringList result; @@ -215,7 +199,7 @@ QStringList TXInfo::allRecipients() const // Get all recipients from this tx (minus change) // Usable for regular tx sign dialog - std::vector> recipientsList; + std::vector> recipientsList; recipientsList = txReq_.getRecipients([this](const bs::Address &addr){ return (addr != txReq_.change.address); }); diff --git a/BlockSettleSigner/TXInfo.h b/BlockSettleSigner/TXInfo.h index 0960c9e78..917474dd7 100644 --- a/BlockSettleSigner/TXInfo.h +++ b/BlockSettleSigner/TXInfo.h @@ -12,7 +12,7 @@ #define __TX_INFO_H__ #include "CoreWallet.h" -#include "ProtobufHeadlessUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" #include "Wallets/SyncWalletsManager.h" #include "Wallets/SyncHDWallet.h" @@ -89,8 +89,6 @@ class TXInfo : public QObject double inputAmountFull() const; double outputAmountFull() const; - Q_INVOKABLE double amountCCReceived(const QString &cc) const; - Q_INVOKABLE double amountCCSent() const; Q_INVOKABLE double amountXBTReceived() const; Q_INVOKABLE bool saveToFile(const QString &fileName) const; diff --git a/BlockSettleSigner/WalletsProxy.cpp b/BlockSettleSigner/WalletsProxy.cpp index acedcb6e2..9585c8725 100644 --- a/BlockSettleSigner/WalletsProxy.cpp +++ b/BlockSettleSigner/WalletsProxy.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -23,7 +23,6 @@ #include "CoreHDWallet.h" #include "PaperBackupWriter.h" #include "SignerAdapter.h" -#include "SignerAdapterContainer.h" #include "TXInfo.h" #include "UiUtils.h" #include "WalletBackupFile.h" @@ -32,7 +31,7 @@ #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" #include "BSErrorCodeStrings.h" -#include "OfflineSigner.h" +#include "Wallets/OfflineSigner.h" #include "signer.pb.h" @@ -289,8 +288,8 @@ bool WalletsProxy::backupPrivateKey(const QString &walletId, QString fileName, b if (!wallet) { throw std::runtime_error("failed to find wallet with id " + walletId.toStdString()); } - const auto& encoded = ArmoryBackups::BackupEasy16::encode(chainCode - , ArmoryBackups::BackupType::BIP32_Seed_Structured); + const auto& encoded = Armory::Backups::BackupEasy16::encode(chainCode + , Armory::Backups::BackupType::BIP32_Seed_Structured); if (encoded.size() != 2) { throw std::runtime_error("failed to encode easy16"); } @@ -604,6 +603,7 @@ void WalletsProxy::deleteWallet(const QString &walletId, const QJSValue &jsCallb std::shared_ptr WalletsProxy::getWoSyncWallet(const bs::sync::WatchingOnlyWallet &wo) const { +#if 0 //FIXME: unknown sync::hd::Wallet constructor try { auto result = std::make_shared(wo, signContainer().get(), logger_); result->setWCT(walletsMgr_.get()); @@ -617,6 +617,7 @@ std::shared_ptr WalletsProxy::getWoSyncWallet(const bs::sy } catch (const std::exception &e) { logger_->error("[WalletsProxy] WO-wallet creation failed: {}", e.what()); } +#endif //0 return nullptr; } diff --git a/BlockSettleSigner/WalletsProxy.h b/BlockSettleSigner/WalletsProxy.h index dfda694d6..88a2dff66 100644 --- a/BlockSettleSigner/WalletsProxy.h +++ b/BlockSettleSigner/WalletsProxy.h @@ -16,7 +16,7 @@ #include #include "BSErrorCode.h" -#include "SignerDefs.h" +#include "Wallets/SignerDefs.h" #include "hwcommonstructure.h" namespace spdlog { diff --git a/BlockSettleSigner/interfaces/GUI_QML/QMLApp.cpp b/BlockSettleSigner/interfaces/GUI_QML/QMLApp.cpp index 2636aff61..a00697208 100644 --- a/BlockSettleSigner/interfaces/GUI_QML/QMLApp.cpp +++ b/BlockSettleSigner/interfaces/GUI_QML/QMLApp.cpp @@ -9,8 +9,6 @@ */ #include "ApplicationSettings.h" -#include "AutheIDClient.h" -#include "AuthProxy.h" #include "ConnectionManager.h" #include "CoreWalletsManager.h" #include "EasyEncValidator.h" @@ -21,14 +19,14 @@ #include "QmlFactory.h" #include "QMLStatusUpdater.h" #include "QmlWalletsViewModel.h" -#include "QPasswordData.h" -#include "QSeed.h" -#include "QWalletInfo.h" +#include "Wallets/QPasswordData.h" +#include "Wallets/QSeed.h" +#include "Wallets/QWalletInfo.h" #include "SignerAdapter.h" #include "Settings/SignerSettings.h" #include "SignerVersion.h" -#include "SignerUiDefs.h" -#include "SignContainer.h" +#include "Wallets/SignerUiDefs.h" +#include "Wallets/SignContainer.h" #include "TXInfo.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" @@ -113,21 +111,19 @@ QMLAppObj::QMLAppObj(SignerAdapter *adapter, const std::shared_ptrrunMode() != bs::signer::ui::RunMode::litegui) { - trayIconOptional_ = new QSystemTrayIcon(QIcon(QStringLiteral(":/images/bs_logo.png")), this); - connect(trayIconOptional_, &QSystemTrayIcon::messageClicked, this, &QMLAppObj::onSysTrayMsgClicked); - connect(trayIconOptional_, &QSystemTrayIcon::activated, this, &QMLAppObj::onSysTrayActivated); + trayIconOptional_ = new QSystemTrayIcon(QIcon(QStringLiteral(":/images/bs_logo.png")), this); + connect(trayIconOptional_, &QSystemTrayIcon::messageClicked, this, &QMLAppObj::onSysTrayMsgClicked); + connect(trayIconOptional_, &QSystemTrayIcon::activated, this, &QMLAppObj::onSysTrayActivated); #ifdef BS_USE_DBUS - if (dbus_->isValid()) { - notifMode_ = Freedesktop; + if (dbus_->isValid()) { + notifMode_ = Freedesktop; - QObject::disconnect(trayIconOptional_, &QSystemTrayIcon::messageClicked, - this, &QMLAppObj::onSysTrayMsgClicked); - connect(dbus_, &DBusNotification::messageClicked, this, &QMLAppObj::onSysTrayMsgClicked); - } -#endif // BS_USE_DBUS + QObject::disconnect(trayIconOptional_, &QSystemTrayIcon::messageClicked, + this, &QMLAppObj::onSysTrayMsgClicked); + connect(dbus_, &DBusNotification::messageClicked, this, &QMLAppObj::onSysTrayMsgClicked); } +#endif // BS_USE_DBUS if (adapter) { settingsConnections(); @@ -214,23 +210,18 @@ void QMLAppObj::registerQtTypes() qRegisterMetaType("QJSValueList"); qRegisterMetaType(); - qRegisterMetaType("AutheIDClient::RequestType"); qRegisterMetaType("EncryptionType"); qRegisterMetaType("QSeed"); - qRegisterMetaType("AuthSignWalletObject"); qmlRegisterUncreatableType("com.blocksettle.WalletsViewModel", 1, 0, "WalletsModel", QStringLiteral("Cannot create a WalletsViewModel instance")); qmlRegisterUncreatableType("com.blocksettle.WalletsProxy", 1, 0, "WalletsProxy", QStringLiteral("Cannot create a WalletesProxy instance")); - qmlRegisterUncreatableType("com.blocksettle.AutheIDClient", 1, 0, - "AutheIDClient", QStringLiteral("Cannot create a AutheIDClient instance")); qmlRegisterUncreatableType("com.blocksettle.QmlFactory", 1, 0, "QmlFactory", QStringLiteral("Cannot create a QmlFactory instance")); qmlRegisterUncreatableType("com.blocksettle.HwDeviceManager", 1, 0, "HwDeviceManager", QStringLiteral("Cannot create a HwDeviceManager instance")); - qmlRegisterType("com.blocksettle.AuthSignWalletObject", 1, 0, "AuthSignWalletObject"); qmlRegisterType("com.blocksettle.TXInfo", 1, 0, "TXInfo"); qmlRegisterType("com.blocksettle.PasswordDialogData", 1, 0, "PasswordDialogData"); qmlRegisterType("com.blocksettle.QmlPdfBackup", 1, 0, "QmlPdfBackup"); diff --git a/BlockSettleSigner/interfaces/GUI_QML/QMLApp.h b/BlockSettleSigner/interfaces/GUI_QML/QMLApp.h index 0ff470b9f..c0df9cfc7 100644 --- a/BlockSettleSigner/interfaces/GUI_QML/QMLApp.h +++ b/BlockSettleSigner/interfaces/GUI_QML/QMLApp.h @@ -15,7 +15,7 @@ #include #include #include -#include "SignerDefs.h" +#include "Wallets/SignerDefs.h" namespace bs { namespace wallet { diff --git a/BlockSettleSigner/interfaces/GUI_QML/QmlWalletsViewModel.h b/BlockSettleSigner/interfaces/GUI_QML/QmlWalletsViewModel.h index c8cb71a34..4b44d8b0e 100644 --- a/BlockSettleSigner/interfaces/GUI_QML/QmlWalletsViewModel.h +++ b/BlockSettleSigner/interfaces/GUI_QML/QmlWalletsViewModel.h @@ -17,7 +17,7 @@ #include #include #include -#include "QWalletInfo.h" +#include "Wallets/QWalletInfo.h" namespace bs { diff --git a/BlockSettleSigner/main.cpp b/BlockSettleSigner/main.cpp index d4ab5a8ab..9d9a50aae 100644 --- a/BlockSettleSigner/main.cpp +++ b/BlockSettleSigner/main.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -46,8 +46,6 @@ #include #include -#include - #include #include #include @@ -327,42 +325,32 @@ static int QMLApp(int argc, char **argv QMLAppObj qmlAppObj(&adapter, logger, settings, splashScreen, engine.rootContext()); - switch (settings->runMode()) { - case bs::signer::ui::RunMode::fullgui: - engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); - break; - case bs::signer::ui::RunMode::litegui: - engine.load(QUrl(QStringLiteral("qrc:/qml/mainLite.qml"))); - - terminalConnectionTimer.setSingleShot(true); - //BST-2786 - terminalConnectionTimer.setInterval(std::chrono::milliseconds{ 25000 }); - - QObject::connect(&adapter, &SignerAdapter::ready, [&timerStarted, &terminalConnectionTimer]() - { - if (!timerStarted) { - terminalConnectionTimer.start(); - timerStarted = true; - } - }); + engine.load(QUrl(QStringLiteral("qrc:/qml/mainLite.qml"))); + terminalConnectionTimer.setSingleShot(true); + //BST-2786 + terminalConnectionTimer.setInterval(std::chrono::milliseconds{ 25000 }); - QObject::connect(&adapter, &SignerAdapter::peerConnected, [&terminalConnected] { - terminalConnected = true; - }); - QObject::connect(&adapter, &SignerAdapter::peerDisconnected, [&terminalConnected] { - terminalConnected = false; - }); - QObject::connect(&terminalConnectionTimer, &QTimer::timeout, [&terminalConnected] { - if (!terminalConnected) { - QCoreApplication::quit(); + QObject::connect(&adapter, &SignerAdapter::ready, [&timerStarted, &terminalConnectionTimer]() + { + if (!timerStarted) { + terminalConnectionTimer.start(); + timerStarted = true; } }); - break; - default: - return EXIT_FAILURE; - } + + QObject::connect(&adapter, &SignerAdapter::peerConnected, [&terminalConnected] { + terminalConnected = true; + }); + QObject::connect(&adapter, &SignerAdapter::peerDisconnected, [&terminalConnected] { + terminalConnected = false; + }); + QObject::connect(&terminalConnectionTimer, &QTimer::timeout, [&terminalConnected] { + if (!terminalConnected) { + QCoreApplication::quit(); + } + }); if (engine.rootObjects().isEmpty()) { throw std::runtime_error("Failed to load main QML file"); @@ -400,7 +388,7 @@ int main(int argc, char** argv) srand(std::time(nullptr)); - btc_ecc_start(); + CryptoECDSA::setupContext(); bs::LogManager logMgr; auto loggerStdout = logMgr.logger("settings"); diff --git a/BlockSettleSigner/AuthProxy.cpp b/BlockSettleSigner/unused/AuthProxy.cpp similarity index 100% rename from BlockSettleSigner/AuthProxy.cpp rename to BlockSettleSigner/unused/AuthProxy.cpp diff --git a/BlockSettleSigner/AuthProxy.h b/BlockSettleSigner/unused/AuthProxy.h similarity index 100% rename from BlockSettleSigner/AuthProxy.h rename to BlockSettleSigner/unused/AuthProxy.h diff --git a/BlockSettleTracker/CMakeLists.txt b/BlockSettleTracker/CMakeLists.txt index 22a0ab6c8..f276b7829 100644 --- a/BlockSettleTracker/CMakeLists.txt +++ b/BlockSettleTracker/CMakeLists.txt @@ -1,7 +1,7 @@ # # # *********************************************************************************** -# * Copyright (C) 2020 - 2020, BlockSettle AB +# * Copyright (C) 2020 - 2021, BlockSettle AB # * Distributed under the GNU Affero General Public License (AGPL v3) # * See LICENSE or http://www.gnu.org/licenses/agpl.html # * @@ -37,7 +37,6 @@ ENDIF() TARGET_LINK_LIBRARIES(${BLOCKSETTLE_TRACKER} ${BS_NETWORK_LIB_NAME} - ${ZMQ_LIB} ${WS_LIB} ${OPENSSL_LIBS} ${OS_SPECIFIC_LIBS_TRACKER} diff --git a/BlockSettleTracker/main.cpp b/BlockSettleTracker/main.cpp index ef654dcee..bdd5b2a8d 100644 --- a/BlockSettleTracker/main.cpp +++ b/BlockSettleTracker/main.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/AddressDetailDialog.cpp b/BlockSettleUILib/AddressDetailDialog.cpp index 6102199cf..de437c613 100644 --- a/BlockSettleUILib/AddressDetailDialog.cpp +++ b/BlockSettleUILib/AddressDetailDialog.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -41,7 +41,7 @@ class IncomingTransactionFilter : public QSortFilterProxyModel } const auto &index = txModel->index(source_row, 0, source_parent); const auto &txItem = txModel->getItem(index); - return (txItem && txItem->isSet() && (txItem->txEntry.value > 0)); + return (txItem && (txItem->txEntry.value > 0)); } }; @@ -58,7 +58,7 @@ class OutgoingTransactionFilter : public QSortFilterProxyModel } const auto &index = txModel->index(source_row, 0, source_parent); const auto &txItem = txModel->getItem(index); - return (txItem && txItem->isSet() && (txItem->txEntry.value < 0)); + return (txItem && (txItem->txEntry.value < 0)); } }; @@ -88,41 +88,33 @@ class AddressTransactionFilter : public QSortFilterProxyModel AddressDetailDialog::AddressDetailDialog(const bs::Address& address - , const std::shared_ptr &wallet - , const std::shared_ptr& walletsManager - , const std::shared_ptr &armory - , const std::shared_ptr &logger, QWidget* parent) + , const std::shared_ptr &logger, bs::core::wallet::Type wltType + , uint64_t balance, uint32_t txn, const QString &walletName + , const std::string &addrIndex, const std::string &comment, QWidget* parent) : QDialog(parent) , ui_(new Ui::AddressDetailDialog()) - , logger_(logger) , address_(address) - , walletsManager_(walletsManager) - , armory_(armory) - , wallet_(wallet) + , logger_(logger) { ui_->setupUi(this); ui_->labelError->hide(); - auto balanceVec = wallet_->getAddrBalance(address); - onAddrBalanceReceived(balanceVec); - - onAddrTxNReceived(wallet_->getAddrTxN(address)); + setBalance(balance, wltType); + onAddrTxNReceived(txn); auto copyButton = ui_->buttonBox->addButton(tr("Copy to clipboard"), QDialogButtonBox::ActionRole); connect(copyButton, &QPushButton::clicked, this, &AddressDetailDialog::onCopyClicked); - ui_->labelWallenName->setText(QString::fromStdString(wallet_->name())); + ui_->labelWalletName->setText(walletName); const auto addressString = QString::fromStdString(address.display()); ui_->labelAddress->setText(addressString); - const auto addrIndex = wallet_->getAddressIndex(address); const auto path = bs::hd::Path::fromString(addrIndex); QString index; if (path.length() != 2) { index = QString::fromStdString(addrIndex); - } - else { + } else { const auto lastIndex = QString::number(path.get(-1)); switch (path.get(-2)) { case 0: @@ -140,31 +132,11 @@ AddressDetailDialog::AddressDetailDialog(const bs::Address& address } ui_->labelAddrIndex->setText(index); - const auto comment = wallet_->getAddressComment(address); ui_->labelComment->setText(QString::fromStdString(comment)); ui_->inputAddressesWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui_->outputAddressesWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - if (armory_->state() != ArmoryState::Ready) { - ui_->labelError->setText(tr("BlockSettleDB is not connected")); - onError(); - } - else { - QPointer thisPtr = this; - const auto &cbLedgerDelegate = [thisPtr, armory](const std::shared_ptr &delegate) { - QMetaObject::invokeMethod(qApp, [thisPtr, delegate]{ - if (thisPtr) { - thisPtr->initModels(delegate); - } - }); - }; - if (!wallet_->getLedgerDelegateForAddress(address_, cbLedgerDelegate)) { - ui_->labelError->setText(tr("Error loading address info")); - onError(); - } - } - const QString addrURI = QLatin1String("bitcoin:") + addressString; ui_->labelQR->setPixmap(UiUtils::getQRCode(addrURI, 128)); @@ -175,43 +147,47 @@ AddressDetailDialog::AddressDetailDialog(const bs::Address& address this, &AddressDetailDialog::onInputAddrContextMenu); connect(ui_->outputAddressesWidget, &QTreeView::customContextMenuRequested, this, &AddressDetailDialog::onOutputAddrContextMenu); -} -AddressDetailDialog::~AddressDetailDialog() = default; - -void AddressDetailDialog::initModels(const std::shared_ptr &delegate) -{ - TransactionsViewModel* model = new TransactionsViewModel(armory_ - , walletsManager_ - , delegate - , logger_ - , wallet_ - , address_ - , this); + model_ = new TransactionsViewModel(logger_, this); + connect(model_, &TransactionsViewModel::needTXDetails, this, &AddressDetailDialog::needTXDetails); IncomingTransactionFilter* incomingFilter = new IncomingTransactionFilter(this); - incomingFilter->setSourceModel(model); + incomingFilter->setSourceModel(model_); AddressTransactionFilter* inFilter = new AddressTransactionFilter(this); inFilter->setSourceModel(incomingFilter); ui_->inputAddressesWidget->setModel(inFilter); ui_->inputAddressesWidget->sortByColumn(static_cast(TransactionsViewModel::Columns::Date), Qt::DescendingOrder); OutgoingTransactionFilter* outgoingFilter = new OutgoingTransactionFilter(this); - outgoingFilter->setSourceModel(model); + outgoingFilter->setSourceModel(model_); AddressTransactionFilter* outFilter = new AddressTransactionFilter(this); outFilter->setSourceModel(outgoingFilter); ui_->outputAddressesWidget->setModel(outFilter); ui_->outputAddressesWidget->sortByColumn(static_cast(TransactionsViewModel::Columns::Date), Qt::DescendingOrder); } -void AddressDetailDialog::onAddrBalanceReceived(const std::vector &balance) +AddressDetailDialog::~AddressDetailDialog() = default; + +void AddressDetailDialog::onNewBlock(unsigned int blockNum) { - if (balance.empty()) { - ui_->labelBalance->setText(QString::number(0)); - return; - } - ui_->labelBalance->setText((wallet_->type() == bs::core::wallet::Type::ColorCoin) - ? UiUtils::displayCCAmount(balance[0]) : UiUtils::displayAmount(balance[0])); + model_->onNewBlock(blockNum); +} + +void AddressDetailDialog::onLedgerEntries(uint32_t curBlock + , const std::vector &entries) +{ + model_->onLedgerEntries({}, 0, 0, curBlock, entries); +} + +void AddressDetailDialog::onTXDetails(const std::vector &details) +{ + model_->onTXDetails(details); +} + +void AddressDetailDialog::setBalance(uint64_t balance, bs::core::wallet::Type wltType) +{ + ui_->labelBalance->setText((wltType == bs::core::wallet::Type::ColorCoin) + ? UiUtils::displayCCAmount(balance) : UiUtils::displayAmount(balance)); } void AddressDetailDialog::onAddrTxNReceived(uint32_t txn) diff --git a/BlockSettleUILib/AddressDetailDialog.h b/BlockSettleUILib/AddressDetailDialog.h index 3205b3c04..0ebd391f6 100644 --- a/BlockSettleUILib/AddressDetailDialog.h +++ b/BlockSettleUILib/AddressDetailDialog.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -15,6 +15,8 @@ #include #include "Address.h" #include "AsyncClient.h" +#include "CoreWallet.h" +#include "Wallets/SignerDefs.h" namespace spdlog { class logger; @@ -29,39 +31,42 @@ namespace bs { } } class ArmoryConnection; -class Tx; +class TransactionsViewModel; class AddressDetailDialog : public QDialog { Q_OBJECT public: - AddressDetailDialog(const bs::Address &address - , const std::shared_ptr &wallet - , const std::shared_ptr& walletsManager - , const std::shared_ptr &armory - , const std::shared_ptr &logger - , QWidget* parent = nullptr ); + AddressDetailDialog(const bs::Address &, const std::shared_ptr & + , bs::core::wallet::Type, uint64_t balance, uint32_t txn + , const QString &walletName, const std::string &addrIndex + , const std::string &comment, QWidget* parent = nullptr); ~AddressDetailDialog() override; + void onNewBlock(unsigned int blockNum); + void onLedgerEntries(uint32_t curBlock, const std::vector &); + void onTXDetails(const std::vector &); + +signals: + void needTXDetails(const std::vector &, bool useCache + , const bs::Address &); + private slots: void onCopyClicked() const; - void onAddrBalanceReceived(const std::vector &); void onAddrTxNReceived(uint32_t); void onInputAddrContextMenu(const QPoint &pos); void onOutputAddrContextMenu(const QPoint &pos); private: + void setBalance(uint64_t, bs::core::wallet::Type); void onError(); - Q_INVOKABLE void initModels(const std::shared_ptr &); private: std::unique_ptr ui_; + const bs::Address address_; std::shared_ptr logger_; - const bs::Address address_; - std::shared_ptr walletsManager_; - std::shared_ptr armory_; - std::shared_ptr wallet_; + TransactionsViewModel *model_{ nullptr }; }; #endif // __ADDRESS_DETAIL_DIALOG_H__ diff --git a/BlockSettleUILib/AddressDetailDialog.ui b/BlockSettleUILib/AddressDetailDialog.ui index 3096d817a..406bd9e2e 100644 --- a/BlockSettleUILib/AddressDetailDialog.ui +++ b/BlockSettleUILib/AddressDetailDialog.ui @@ -2,7 +2,7 @@ ApiKeyEntryDialog diff --git a/BlockSettleUILib/AuthAddressConfirmDialog.cpp b/BlockSettleUILib/AuthAddressConfirmDialog.cpp deleted file mode 100644 index ea4247768..000000000 --- a/BlockSettleUILib/AuthAddressConfirmDialog.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "AuthAddressConfirmDialog.h" - -#include "AuthAddressConfirmDialog.h" -#include "BsClient.h" -#include "BSMessageBox.h" -#include "ApplicationSettings.h" -#include "ui_AuthAddressConfirmDialog.h" - -namespace { - constexpr auto UiTimerInterval = std::chrono::milliseconds(250); -} - -AuthAddressConfirmDialog::AuthAddressConfirmDialog(const std::weak_ptr &bsClient, const bs::Address& address - , const std::shared_ptr& authManager, const std::shared_ptr &settings, QWidget* parent) - : QDialog(parent) - , ui_{new Ui::AuthAddressConfirmDialog()} - , address_{address} - , authManager_{authManager} - , settings_(settings) - , bsClient_(bsClient) -{ - ui_->setupUi(this); - - setWindowTitle(tr("Auth eID Signing Request")); - ui_->labelDescription->setText(QString::fromStdString(address.display())); - - connect(ui_->pushButtonCancel, &QPushButton::clicked, this, &AuthAddressConfirmDialog::onCancelPressed); - - // setup timer - progressTimer_.setSingleShot(false); - progressTimer_.setInterval(UiTimerInterval); - - ui_->progressBarTimeout->setMinimum(0); - const int timeoutMs = int(std::chrono::duration_cast(BsClient::autheidAuthAddressTimeout()).count()); - ui_->progressBarTimeout->setMaximum(timeoutMs); - ui_->progressBarTimeout->setValue(ui_->progressBarTimeout->maximum()); - ui_->progressBarTimeout->setFormat(QString()); - - connect(&progressTimer_, &QTimer::timeout, this, &AuthAddressConfirmDialog::onUiTimerTick); - - // connect to auth manager - connect(authManager_.get(), &AuthAddressManager::AuthAddressSubmitError, this, &AuthAddressConfirmDialog::onAuthAddressSubmitError, Qt::QueuedConnection); - connect(authManager_.get(), &AuthAddressManager::AuthAddressSubmitSuccess, this, &AuthAddressConfirmDialog::onAuthAddressSubmitSuccess, Qt::QueuedConnection); - connect(authManager_.get(), &AuthAddressManager::AuthAddressSubmitCancelled, this, &AuthAddressConfirmDialog::onAuthAddressSubmitCancelled, Qt::QueuedConnection); - - // send confirm request - startTime_ = std::chrono::steady_clock::now(); - authManager_->ConfirmSubmitForVerification(bsClient, address); - progressTimer_.start(); -} - -AuthAddressConfirmDialog::~AuthAddressConfirmDialog() = default; - -void AuthAddressConfirmDialog::onUiTimerTick() -{ - // Do not check timeout here, BsClient will detect it itself - - const auto timeLeft = BsClient::autheidAuthAddressTimeout() - (std::chrono::steady_clock::now() - startTime_); - - const int countMs = std::max(0, int(std::chrono::duration_cast(timeLeft).count())); - ui_->progressBarTimeout->setValue(countMs); - ui_->labelTimeLeft->setText(tr("%1 seconds left").arg(countMs / 1000)); -} - -void AuthAddressConfirmDialog::onCancelPressed() -{ - reject(); -} - -void AuthAddressConfirmDialog::reject() -{ - progressTimer_.stop(); - - auto bsClient = bsClient_.lock(); - if (bsClient) { - bsClient->cancelActiveSign(); - } - - QDialog::reject(); -} - -void AuthAddressConfirmDialog::onAuthAddressSubmitCancelled(const QString &address) -{ - reject(); -} - -void AuthAddressConfirmDialog::onError(const QString &errorText) -{ - // error message should be displayed by parent - hide(); - parentWidget()->setFocus(); - reject(); -} - -void AuthAddressConfirmDialog::onAuthAddressSubmitError(const QString &address, const bs::error::AuthAddressSubmitResult statusCode) -{ - progressTimer_.stop(); - - QString errorText; - - switch (statusCode) { - case bs::error::AuthAddressSubmitResult::SubmitLimitExceeded: - errorText = tr("Your account has reached the limit of how many " - "Authentication Addresses it may have in submitted state." - " Please verify your currently submitted addresses before " - "submitting further addresses."); - break; - case bs::error::AuthAddressSubmitResult::ServerError: - errorText = tr("Server error. Please try again later."); - break; - case bs::error::AuthAddressSubmitResult::AlreadyUsed: - errorText = tr("Authentication Address already in use."); - break; - case bs::error::AuthAddressSubmitResult::RequestTimeout: - errorText = tr("Request processing timeout."); - break; - case bs::error::AuthAddressSubmitResult::AuthRequestSignFailed: - errorText = tr("Failed to sign request to submit Authentication Address."); - break; - default: - errorText = tr("Undefined error code."); - break; - } - - BSMessageBox(BSMessageBox::critical, tr("Submission") - , tr("Submission failed") - , errorText, this).exec(); - reject(); -} - -void AuthAddressConfirmDialog::onAuthAddressSubmitSuccess(const QString &address) -{ - progressTimer_.stop(); - - const bool isProd = settings_->get(ApplicationSettings::envConfiguration) == - static_cast(ApplicationSettings::EnvConfiguration::Production); - - BSMessageBox(BSMessageBox::success, tr("Submission Successful") - , tr("Authentication Address Submitted") - , tr("You now have access to Spot XBT (bitcoin) trading.") - , this).exec(); - accept(); -} diff --git a/BlockSettleUILib/AuthAddressConfirmDialog.h b/BlockSettleUILib/AuthAddressConfirmDialog.h deleted file mode 100644 index 02bc537af..000000000 --- a/BlockSettleUILib/AuthAddressConfirmDialog.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef AUTH_ADDRESS_CONFIRMATION_DIALOG_H__ -#define AUTH_ADDRESS_CONFIRMATION_DIALOG_H__ - -#include "AuthAddressManager.h" - -#include -#include -#include -#include - -#include - -class BsClient; -class ApplicationSettings; -namespace Ui { - class AuthAddressConfirmDialog; -}; - -class AuthAddressConfirmDialog : public QDialog -{ -Q_OBJECT - -public: - AuthAddressConfirmDialog(const std::weak_ptr &bsClient, - const bs::Address& address, - const std::shared_ptr& authManager, - const std::shared_ptr &settings, - QWidget* parent = nullptr); - ~AuthAddressConfirmDialog() override; - -private slots: - void onUiTimerTick(); - void onCancelPressed(); - - void onError(const QString &errorText); - void onAuthAddressSubmitError(const QString &address, const bs::error::AuthAddressSubmitResult statusCode); - void onAuthAddressSubmitSuccess(const QString &address); - void onAuthAddressSubmitCancelled(const QString &address); - -private: - void reject() override; - -private: - std::unique_ptr ui_; - - bs::Address address_; - std::shared_ptr authManager_; - std::shared_ptr settings_; - - QTimer progressTimer_; - std::chrono::steady_clock::time_point startTime_; - - std::weak_ptr bsClient_; -}; - -#endif // AUTH_ADDRESS_CONFIRMATION_DIALOG_H__ diff --git a/BlockSettleUILib/AuthAddressConfirmDialog.ui b/BlockSettleUILib/AuthAddressConfirmDialog.ui deleted file mode 100644 index 86b4626c9..000000000 --- a/BlockSettleUILib/AuthAddressConfirmDialog.ui +++ /dev/null @@ -1,265 +0,0 @@ - - - - AuthAddressConfirmDialog - - - - 0 - 0 - 376 - 175 - - - - - 0 - 0 - - - - Dialog - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 15 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - SUBMIT AUTHENTICATION ADDRESS - - - - 6 - - - 0 - - - 2 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Address - - - true - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 0 - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - SIGN WITH AUTH EID - - - - 6 - - - 0 - - - 2 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 6 - - - - - 16777215 - 6 - - - - false - - - - - - - - 0 - 0 - - - - - - - Qt::AlignCenter - - - - - - - - - - - - - - 0 - 0 - - - - true - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - Cancel - - - true - - - true - - - - - - - - - - - diff --git a/BlockSettleUILib/AuthAddressDialog.cpp b/BlockSettleUILib/AuthAddressDialog.cpp deleted file mode 100644 index b4ed294e0..000000000 --- a/BlockSettleUILib/AuthAddressDialog.cpp +++ /dev/null @@ -1,439 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "AuthAddressDialog.h" -#include "ui_AuthAddressDialog.h" - -#include -#include -#include -#include -#include -#include - -#include "ApplicationSettings.h" -#include "AssetManager.h" -#include "AuthAddressConfirmDialog.h" -#include "AuthAddressManager.h" -#include "AuthAddressViewModel.h" -#include "BSMessageBox.h" -#include "UiUtils.h" - - -AuthAddressDialog::AuthAddressDialog(const std::shared_ptr &logger - , const std::shared_ptr &authAddressManager - , const std::shared_ptr &assetMgr - , const std::shared_ptr &settings, QWidget* parent) - : QDialog(parent) - , ui_(new Ui::AuthAddressDialog()) - , logger_(logger) - , authAddressManager_(authAddressManager) - , assetManager_(assetMgr) - , settings_(settings) -{ - ui_->setupUi(this); - - auto *originModel = new AuthAddressViewModel(authAddressManager_, ui_->treeViewAuthAdress); - model_ = new AuthAdressControlProxyModel(originModel, this); - model_->setVisibleRowsCount(settings_->get(ApplicationSettings::numberOfAuthAddressVisible)); - ui_->treeViewAuthAdress->setModel(model_); - ui_->treeViewAuthAdress->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - ui_->treeViewAuthAdress->installEventFilter(this); - - connect(ui_->treeViewAuthAdress->selectionModel(), &QItemSelectionModel::selectionChanged - , this, &AuthAddressDialog::adressSelected); - connect(model_, &AuthAdressControlProxyModel::modelReset, this, &AuthAddressDialog::onModelReset); - connect(originModel, &AuthAddressViewModel::updateSelectionAfterReset, this, &AuthAddressDialog::onUpdateSelection); - - connect(authAddressManager_.get(), &AuthAddressManager::AddrVerifiedOrRevoked, this, &AuthAddressDialog::onAddressStateChanged, Qt::QueuedConnection); - connect(authAddressManager_.get(), &AuthAddressManager::Error, this, &AuthAddressDialog::onAuthMgrError, Qt::QueuedConnection); - connect(authAddressManager_.get(), &AuthAddressManager::Info, this, &AuthAddressDialog::onAuthMgrInfo, Qt::QueuedConnection); - - connect(ui_->pushButtonCreate, &QPushButton::clicked, this, &AuthAddressDialog::createAddress); - connect(ui_->pushButtonRevoke, &QPushButton::clicked, this, &AuthAddressDialog::revokeSelectedAddress); - connect(ui_->pushButtonSubmit, &QPushButton::clicked, this, &AuthAddressDialog::submitSelectedAddress); - connect(ui_->pushButtonDefault, &QPushButton::clicked, this, &AuthAddressDialog::setDefaultAddress); -} - -AuthAddressDialog::~AuthAddressDialog() = default; - -bool AuthAddressDialog::eventFilter(QObject* sender, QEvent* event) -{ - if (sender == ui_->treeViewAuthAdress) { - if (QEvent::KeyPress == event->type()) { - QKeyEvent* keyEvent = static_cast(event); - - if (keyEvent->matches(QKeySequence::Copy)) { - copySelectedToClipboard(); - return true; - } - } - else if (QEvent::ContextMenu == event->type()) { - QContextMenuEvent* contextMenuEvent = static_cast(event); - - QPoint pos = contextMenuEvent->pos(); - pos.setY(pos.y() - ui_->treeViewAuthAdress->header()->height()); - const auto index = ui_->treeViewAuthAdress->indexAt(pos); - if (index.isValid()) { - if (ui_->treeViewAuthAdress->selectionModel()->selectedIndexes()[0] != index) { - ui_->treeViewAuthAdress->selectionModel()->select(index, - QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - } - - QMenu menu; - menu.addAction(tr("&Copy Authentication Address"), [this]() { - copySelectedToClipboard(); - }); - menu.exec(contextMenuEvent->globalPos()); - return true; - } - } - } - - return QWidget::eventFilter(sender, event); -} - -void AuthAddressDialog::showEvent(QShowEvent *evt) -{ - if (defaultAddr_.empty()) { - defaultAddr_ = authAddressManager_->getDefault(); - if (defaultAddr_.empty() && authAddressManager_->GetAddressCount()) { - defaultAddr_ = authAddressManager_->GetAddress(0); - } - model_->setDefaultAddr(defaultAddr_); - } - - updateUnsubmittedState(); - - ui_->treeViewAuthAdress->selectionModel()->clearSelection(); - - QModelIndex index = model_->getFirstUnsubmitted(); - if (!index.isValid() && !model_->isEmpty()) { - // get first if none unsubmitted - index = model_->index(0, 0); - } - - if (index.isValid()) { - ui_->treeViewAuthAdress->selectionModel()->select( - index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - } - - ui_->labelHint->clear(); - - resizeTreeViewColumns(); - - QDialog::showEvent(evt); -} - -bool AuthAddressDialog::unsubmittedExist() const -{ - return unconfirmedExists_; -} - -void AuthAddressDialog::updateUnsubmittedState() -{ - for (size_t i = 0; i < authAddressManager_->GetAddressCount(); i++) { - const auto authAddr = authAddressManager_->GetAddress(i); - if (!authAddressManager_->hasSettlementLeaf(authAddr)) { - authAddressManager_->createSettlementLeaf(authAddr, [] {}); - unconfirmedExists_ = true; - return; - } - switch (authAddressManager_->GetState(authAddr)) { - case AuthAddressManager::AuthAddressState::Verifying: - unconfirmedExists_ = true; - break; - default: - unconfirmedExists_ = false; - break; - } - } -} - -void AuthAddressDialog::onAuthMgrError(const QString &details) -{ - showError(tr("Authentication Address error"), details); -} - -void AuthAddressDialog::onAuthMgrInfo(const QString &text) -{ - showInfo(tr("Authentication Address"), text); -} - -void AuthAddressDialog::showError(const QString &text, const QString &details) -{ - BSMessageBox errorMessage(BSMessageBox::critical, tr("Error"), text, details, this); - errorMessage.exec(); -} - -void AuthAddressDialog::showInfo(const QString &title, const QString &text) -{ - BSMessageBox(BSMessageBox::info, title, text).exec(); -} - -void AuthAddressDialog::setAddressToVerify(const QString &addr) -{ - if (addr.isEmpty()) { - setWindowTitle(tr("Authentication Addresses")); - } else { - setWindowTitle(tr("Address %1 requires verification").arg(addr)); - for (int i = 0; i < model_->rowCount(); i++) { - if (addr == model_->data(model_->index(i, 0))) { - const auto &index = model_->index(i, 0); - ui_->treeViewAuthAdress->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - ui_->treeViewAuthAdress->scrollTo(index); - break; - } - } - - ui_->labelHint->setText(tr("Your Authentication Address can now be Verified. Please press Verify and enter your password to execute the address verification.")); - ui_->treeViewAuthAdress->setFocus(); - } -} - -void AuthAddressDialog::setBsClient(const std::weak_ptr& bsClient) -{ - bsClient_ = bsClient; -} - -void AuthAddressDialog::onAuthVerifyTxSent() -{ - BSMessageBox(BSMessageBox::info, tr("Authentication Address") - , tr("Verification Transaction successfully sent.") - , tr("Once the validation transaction is mined six (6) blocks deep, your Authentication Address will be" - " accepted as valid by the network and you can enter orders in the Spot XBT product group.")).exec(); - accept(); -} - -void AuthAddressDialog::onUpdateSelection(int row) -{ - if (row < 0 || row >= model_->rowCount()) { - return; - } - - ui_->treeViewAuthAdress->selectionModel()->select( - model_->index(row, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); -} - -void AuthAddressDialog::copySelectedToClipboard() -{ - auto *selectionModel = ui_->treeViewAuthAdress->selectionModel(); - if (!selectionModel->hasSelection()) { - return; - } - - auto const address = model_->getAddress(selectionModel->selectedIndexes()[0]); - qApp->clipboard()->setText(QString::fromStdString(address.display())); -} - - -void AuthAddressDialog::onAddressStateChanged(const QString &addr, const QString &state) -{ - if (state == QStringLiteral("Verified")) { - BSMessageBox(BSMessageBox::success, tr("Authentication Address") - , tr("Authentication Address verified") - , tr("You may now place orders in the Spot XBT product group.") - ).exec(); - } else if (state == QStringLiteral("Revoked")) { - BSMessageBox(BSMessageBox::warning, tr("Authentication Address") - , tr("Authentication Address revoked") - , tr("Authentication Address %1 was revoked and could not be used for Spot XBT trading.").arg(addr)).exec(); - } - updateEnabledStates(); -} - -void AuthAddressDialog::resizeTreeViewColumns() -{ - if (!ui_->treeViewAuthAdress->model()) { - return; - } - - for (int i = ui_->treeViewAuthAdress->model()->columnCount() - 1; i >= 0; --i) { - ui_->treeViewAuthAdress->resizeColumnToContents(i); - } -} - -void AuthAddressDialog::adressSelected() -{ - updateEnabledStates(); -} - -bs::Address AuthAddressDialog::GetSelectedAddress() const -{ - auto selectedRows = ui_->treeViewAuthAdress->selectionModel()->selectedRows(); - if (!selectedRows.isEmpty()) { - return model_->getAddress(selectedRows[0]); - } - - return bs::Address(); -} - -void AuthAddressDialog::createAddress() -{ - if (authAddressManager_->GetAddressCount() > model_->getVisibleRowsCount()) { - // We already have address but they is no visible in view - model_->increaseVisibleRowsCountByOne(); - saveAddressesNumber(); - onModelReset(); - return; - } - - if (!authAddressManager_->CreateNewAuthAddress()) { - showError(tr("Failed to create new address"), tr("Auth wallet error")); - } else { - ui_->pushButtonCreate->setEnabled(false); - model_->increaseVisibleRowsCountByOne(); - saveAddressesNumber(); - } -} - -void AuthAddressDialog::revokeSelectedAddress() -{ - auto selectedAddress = GetSelectedAddress(); - if (selectedAddress.empty()) { - return; - } - - BSMessageBox revokeQ(BSMessageBox::question, tr("Authentication Address") - , tr("Revoke Authentication Address") - , tr("Your Authentication Address\n" - "%1\n" - "will be revoked. Are you sure you wish to continue?") - .arg(QString::fromStdString(selectedAddress.display())) - , this); - if (revokeQ.exec() == QDialog::Accepted) { - authAddressManager_->RevokeAddress(selectedAddress); - } -} - -void AuthAddressDialog::submitSelectedAddress() -{ - ui_->pushButtonSubmit->setEnabled(false); - ui_->labelHint->clear(); - - const auto selectedAddress = GetSelectedAddress(); - if (selectedAddress.empty()) { - return; - } - - const auto state = authAddressManager_->GetState(selectedAddress); - if (state != AuthAddressManager::AuthAddressState::NotSubmitted) { - SPDLOG_LOGGER_ERROR(logger_, "refuse to submit address in state: {}", (int)state); - return; - } - - auto bsClient = bsClient_.lock(); - - if (!bsClient) { - SPDLOG_LOGGER_ERROR(logger_, "bsClient_ in not set"); - return; - } - - setLastSubmittedAddress(selectedAddress); - - AuthAddressConfirmDialog confirmDlg{bsClient_, lastSubmittedAddress_, authAddressManager_, settings_, this}; - - auto result = confirmDlg.exec(); - - setLastSubmittedAddress({}); - - if (result == QDialog::Accepted) { - accept(); - } -} - -void AuthAddressDialog::setDefaultAddress() -{ - auto selectedAddress = GetSelectedAddress(); - if (!selectedAddress.empty()) { - defaultAddr_ = selectedAddress; - model_->setDefaultAddr(defaultAddr_); - authAddressManager_->setDefault(defaultAddr_); - resizeTreeViewColumns(); - } -} - -void AuthAddressDialog::onModelReset() -{ - updateEnabledStates(); - saveAddressesNumber(); - adressSelected(); -} - -void AuthAddressDialog::saveAddressesNumber() -{ - const int newNumber = std::max(1, model_->rowCount()); - const int oldNumber = settings_->get(ApplicationSettings::numberOfAuthAddressVisible); - if (newNumber == oldNumber) { - return; // nothing to save - } - else if (newNumber > oldNumber) { - // we have added address - emit onUpdateSelection(model_->rowCount() - 1); - } - - settings_->set(ApplicationSettings::numberOfAuthAddressVisible, newNumber); - settings_->SaveSettings(); - - if (model_->isEmpty()) { - model_->setVisibleRowsCount(1); - } -} - -void AuthAddressDialog::setLastSubmittedAddress(const bs::Address &address) -{ - if (lastSubmittedAddress_ != address) { - lastSubmittedAddress_ = address; - updateEnabledStates(); - } -} - -void AuthAddressDialog::updateEnabledStates() -{ - const auto selectionModel = ui_->treeViewAuthAdress->selectionModel(); - if (selectionModel->hasSelection()) { - const auto address = model_->getAddress(selectionModel->selectedRows()[0]); - - const bool allowSubmit = authAddressManager_->UserCanSubmitAuthAddress(); - - switch (authAddressManager_->GetState(address)) { - case AuthAddressManager::AuthAddressState::NotSubmitted: - ui_->pushButtonRevoke->setEnabled(false); - ui_->pushButtonSubmit->setEnabled(lastSubmittedAddress_.empty() && allowSubmit); - ui_->pushButtonDefault->setEnabled(false); - break; - case AuthAddressManager::AuthAddressState::Submitted: - case AuthAddressManager::AuthAddressState::Tainted: - case AuthAddressManager::AuthAddressState::Verifying: - case AuthAddressManager::AuthAddressState::Revoked: - case AuthAddressManager::AuthAddressState::RevokedByBS: - case AuthAddressManager::AuthAddressState::Invalid: - ui_->pushButtonRevoke->setEnabled(false); - ui_->pushButtonSubmit->setEnabled(false); - ui_->pushButtonDefault->setEnabled(false); - break; - case AuthAddressManager::AuthAddressState::Verified: - ui_->pushButtonRevoke->setEnabled(authAddressManager_->readyError() == AuthAddressManager::ReadyError::NoError); - ui_->pushButtonSubmit->setEnabled(false); - ui_->pushButtonDefault->setEnabled(address != defaultAddr_); - break; - default: - break; - } - } - else { - ui_->pushButtonRevoke->setEnabled(false); - ui_->pushButtonSubmit->setEnabled(false); - ui_->pushButtonDefault->setEnabled(false); - } - - ui_->pushButtonCreate->setEnabled(lastSubmittedAddress_.empty() && - model_ && !model_->isUnsubmittedAddressVisible()); -} diff --git a/BlockSettleUILib/AuthAddressDialog.h b/BlockSettleUILib/AuthAddressDialog.h deleted file mode 100644 index 53d41a90f..000000000 --- a/BlockSettleUILib/AuthAddressDialog.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __AUTH_ADDRESS_DIALOG_H__ -#define __AUTH_ADDRESS_DIALOG_H__ - -#include "AuthAddressManager.h" -#include "BinaryData.h" -#include "BsClient.h" -#include "ValidityFlag.h" - -#include - -#include -#include - -class AssetManager; -class AuthAdressControlProxyModel; -class QItemSelection; - -namespace Ui { - class AuthAddressDialog; -} - -namespace bs { - struct TradeSettings; -} - -namespace spdlog { - class logger; -} - - -class AuthAddressDialog : public QDialog -{ -Q_OBJECT - -public: - AuthAddressDialog(const std::shared_ptr &logger - , const std::shared_ptr& authAddressManager - , const std::shared_ptr & - , const std::shared_ptr &, QWidget* parent = nullptr); - ~AuthAddressDialog() override; - - void setAddressToVerify(const QString &addr); - void setBsClient(const std::weak_ptr& bsClient); - -signals: - void askForConfirmation(const QString &address, double txAmount); - -private slots: - void resizeTreeViewColumns(); - void adressSelected(); - - void createAddress(); - void revokeSelectedAddress(); - void submitSelectedAddress(); - void setDefaultAddress(); - - void onModelReset(); - void onAddressStateChanged(const QString &addr, const QString &state); - - void onAuthMgrError(const QString &details); - void onAuthMgrInfo(const QString &text); - - void onAuthVerifyTxSent(); - void onUpdateSelection(int row); - void copySelectedToClipboard(); - -protected: - void showEvent(QShowEvent *) override; - void showError(const QString &text, const QString &details = {}); - void showInfo(const QString &title, const QString &text); - bool eventFilter(QObject* sender, QEvent* event) override; - -private: - bs::Address GetSelectedAddress() const; - bool unsubmittedExist() const; - void updateUnsubmittedState(); - void saveAddressesNumber(); - - void setLastSubmittedAddress(const bs::Address &address); - void updateEnabledStates(); - -private: - std::unique_ptr ui_; - std::shared_ptr logger_; - std::shared_ptr authAddressManager_; - std::shared_ptr assetManager_; - std::shared_ptr settings_; - QPointer model_; - bs::Address defaultAddr_; - std::weak_ptr bsClient_; - ValidityFlag validityFlag_; - - bs::Address lastSubmittedAddress_{}; - - bool unconfirmedExists_ = false; -}; - -#endif // __AUTH_ADDRESS_DIALOG_H__ diff --git a/BlockSettleUILib/AuthAddressDialog.ui b/BlockSettleUILib/AuthAddressDialog.ui deleted file mode 100644 index 7bfb05e59..000000000 --- a/BlockSettleUILib/AuthAddressDialog.ui +++ /dev/null @@ -1,313 +0,0 @@ - - - - AuthAddressDialog - - - - 0 - 0 - 556 - 320 - - - - - 0 - 0 - - - - - 556 - 320 - - - - - 556 - 320 - - - - Authentication Addresses - - - - :/resources/brand-logo.png:/resources/brand-logo.png - - - - 15 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 10 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Select Address - - - - 6 - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Submit an Authentication Address for access to bitcoin trading. - - - Qt::PlainText - - - true - - - - - - - - 0 - 0 - - - - Qt::NoContextMenu - - - true - - - false - - - false - - - - - - - - - - Qt::PlainText - - - true - - - - - - - - - - - - - true - - - - 10 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Qt::Horizontal - - - - 400 - 20 - - - - - - - - false - - - - 0 - 0 - - - - - 110 - 35 - - - - Set as Default - - - false - - - - - - - false - - - - 0 - 0 - - - - - 110 - 35 - - - - Revoke - - - false - - - - - - - - 0 - 0 - - - - - 110 - 35 - - - - Create Address - - - false - - - true - - - - - - - false - - - - 0 - 0 - - - - - 110 - 35 - - - - Submit - - - false - - - true - - - - - - - - - - - - - - diff --git a/BlockSettleUILib/AuthAddressViewModel.cpp b/BlockSettleUILib/AuthAddressViewModel.cpp deleted file mode 100644 index 0665ca981..000000000 --- a/BlockSettleUILib/AuthAddressViewModel.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include -#include -#include "AuthAddressViewModel.h" -#include "EncryptionUtils.h" - - -AuthAddressViewModel::AuthAddressViewModel(const std::shared_ptr& authManager, QObject *parent) - : QAbstractItemModel(parent) - , authManager_(authManager) - , defaultAddr_(authManager->getDefault()) -{ - connect(authManager_.get(), &AuthAddressManager::AddressListUpdated, this, &AuthAddressViewModel::onAddressListUpdated, Qt::QueuedConnection); - connect(authManager_.get(), &AuthAddressManager::AuthWalletChanged, this, &AuthAddressViewModel::onAddressListUpdated, Qt::QueuedConnection); -} - -AuthAddressViewModel::~AuthAddressViewModel() noexcept = default; - -int AuthAddressViewModel::columnCount(const QModelIndex&) const -{ - return static_cast(AuthAddressViewColumns::ColumnsCount); -} - -int AuthAddressViewModel::rowCount(const QModelIndex&) const -{ - return addresses_.size(); -} - -QVariant AuthAddressViewModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= addresses_.size()) { - return {}; - } - - const auto address = addresses_[index.row()]; - - if (role == Qt::DisplayRole) { - switch(static_cast(index.column())) { - case AuthAddressViewColumns::ColumnName: - return QString::fromStdString(address.display()); - case AuthAddressViewColumns::ColumnState: - switch (authManager_->GetState(address)) { - case AuthAddressManager::AuthAddressState::Unknown: - return tr("Loading state..."); - case AuthAddressManager::AuthAddressState::NotSubmitted: - return tr("Not Submitted"); - case AuthAddressManager::AuthAddressState::Submitted: - return tr("Submitted"); - case AuthAddressManager::AuthAddressState::Tainted: - return tr("Not Submitted"); - case AuthAddressManager::AuthAddressState::Verifying: - return tr("Verification pending"); - case AuthAddressManager::AuthAddressState::Verified: - return tr("Verified"); - case AuthAddressManager::AuthAddressState::Revoked: - return tr("Revoked"); - case AuthAddressManager::AuthAddressState::RevokedByBS: - return tr("Invalidated by BS"); - case AuthAddressManager::AuthAddressState::Invalid: - return tr("State loading failed"); - } - default: - return {}; - } - } - else if (role == Qt::FontRole) { - if (!defaultAddr_.empty() && (address.prefixed() == defaultAddr_.prefixed())) { - QFont font; - font.setBold(true); - return font; - } - } - - return {}; -} - -QVariant AuthAddressViewModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation != Qt::Horizontal) { - return QVariant(); - } - - if (role == Qt::DisplayRole) { - switch(static_cast(section)) { - case AuthAddressViewColumns::ColumnName: - return tr("Address"); - case AuthAddressViewColumns::ColumnState: - return tr("Status"); - default: - return QVariant(); - } - } - - return QVariant(); -} - -QModelIndex AuthAddressViewModel::index(int row, int column, const QModelIndex&) const -{ - if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { - return QModelIndex(); - } - - return createIndex(row, column); -} - -QModelIndex AuthAddressViewModel::parent(const QModelIndex&) const -{ - return {}; -} - -bs::Address AuthAddressViewModel::getAddress(const QModelIndex& index) const -{ - if (!index.isValid() || index.row() < 0 || index.row() >= addresses_.size()) { - return {}; - } - - return addresses_[index.row()]; -} - -bool AuthAddressViewModel::isAddressNotSubmitted(int row) const -{ - if (row < 0 || row >= addresses_.size()) { - return false; - } - - const auto& address = addresses_[row]; - const auto addrState = authManager_->GetState(address); - return addrState == AuthAddressManager::AuthAddressState::NotSubmitted || - addrState == AuthAddressManager::AuthAddressState::Tainted; -} - -void AuthAddressViewModel::setDefaultAddr(const bs::Address &addr) -{ - defaultAddr_ = addr; - for (int i = 0; i < addresses_.size(); ++i) { - if (addresses_[i].prefixed() == defaultAddr_.prefixed()) { - emit dataChanged(index(i, 0), index(i, 0), { Qt::FontRole }); - return; - } - } -} - -void AuthAddressViewModel::onAddressListUpdated() -{ - // store selection - const auto treeView = qobject_cast(QObject::parent()); - std::pair selectedRowToName; - if (treeView && treeView->selectionModel() && treeView->selectionModel()->hasSelection()) { - selectedRowToName.first = treeView->selectionModel()->selectedRows()[0].row(); - selectedRowToName.second = getAddress(index(selectedRowToName.first, - static_cast(AuthAddressViewColumns::ColumnName))).display(); - } - - // do actual update - const int sizeBeforeReset = addresses_.size(); - emit beginResetModel(); - addresses_.clear(); - const int total = authManager_->GetAddressCount(); - addresses_.reserve(total); - for (int i = 0; i < total; ++i) { - addresses_.push_back(authManager_->GetAddress(i)); - } - emit endResetModel(); - - // restore selection if needed - if (sizeBeforeReset >= addresses_.size() - && selectedRowToName.first < addresses_.size() - && selectedRowToName.second == addresses_[selectedRowToName.first].display()) { - emit updateSelectionAfterReset(selectedRowToName.first); - } -} - -AuthAdressControlProxyModel::AuthAdressControlProxyModel(AuthAddressViewModel *sourceModel, QWidget *parent) - : QSortFilterProxyModel(parent) - , sourceModel_(sourceModel) -{ - setDynamicSortFilter(true); - setSourceModel(sourceModel_); -} - -AuthAdressControlProxyModel::~AuthAdressControlProxyModel() = default; - -void AuthAdressControlProxyModel::setVisibleRowsCount(int rows) -{ - visibleRowsCount_ = rows; - invalidate(); -} - -void AuthAdressControlProxyModel::increaseVisibleRowsCountByOne() -{ - ++visibleRowsCount_; - invalidate(); -} - -int AuthAdressControlProxyModel::getVisibleRowsCount() const -{ - return visibleRowsCount_; -} - -void AuthAdressControlProxyModel::setDefaultAddr(const bs::Address &addr) -{ - sourceModel_->setDefaultAddr(addr); -} - -bs::Address AuthAdressControlProxyModel::getAddress(const QModelIndex& index) const -{ - if (!index.isValid()) { - return {}; - } - - const auto& sourceIndex = mapToSource(index); - return sourceModel_->getAddress(sourceIndex); -} - -bool AuthAdressControlProxyModel::isEmpty() const -{ - return rowCount() == 0; -} - -QModelIndex AuthAdressControlProxyModel::getFirstUnsubmitted() const -{ - if (isEmpty()) { - return {}; - } - - for (int i = 0; i < rowCount(); ++i) { - if (sourceModel_->isAddressNotSubmitted(i)) { - return index(i, 0); - } - } - - return {}; -} - -bool AuthAdressControlProxyModel::isUnsubmittedAddressVisible() const -{ - if (isEmpty()) { - return false; - } - - for (int i = 0; i < visibleRowsCount_; ++i) { - if (sourceModel_->isAddressNotSubmitted(i)) { - return true; - } - } - - return false; -} - -bool AuthAdressControlProxyModel::filterAcceptsRow(int row, const QModelIndex&) const -{ - return visibleRowsCount_ > row; -} diff --git a/BlockSettleUILib/AuthAddressViewModel.h b/BlockSettleUILib/AuthAddressViewModel.h deleted file mode 100644 index 0679974c7..000000000 --- a/BlockSettleUILib/AuthAddressViewModel.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __AUTH_ADDRESS_MODEL_H__ -#define __AUTH_ADDRESS_MODEL_H__ - -#include -#include -#include - -#include - -#include "AuthAddress.h" -#include "AuthAddressManager.h" -#include "BinaryData.h" - -class AuthAddressViewModel : public QAbstractItemModel -{ - Q_OBJECT -public: - AuthAddressViewModel(const std::shared_ptr& authManager, QObject *parent = nullptr); - ~AuthAddressViewModel() noexcept override; - - AuthAddressViewModel(const AuthAddressViewModel&) = delete; - AuthAddressViewModel& operator = (const AuthAddressViewModel&) = delete; - - AuthAddressViewModel(AuthAddressViewModel&&) = delete; - AuthAddressViewModel& operator = (AuthAddressViewModel&&) = delete; - - bs::Address getAddress(const QModelIndex& index) const; - bool isAddressNotSubmitted(int row) const; - void setDefaultAddr(const bs::Address &addr); - -public: - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &child) const override; - -private slots : - void onAddressListUpdated(); - -signals: - void updateSelectionAfterReset(int row); - -private: - std::shared_ptr authManager_; - -private: - enum AuthAddressViewColumns : int - { - ColumnName, - ColumnState, - ColumnsCount - }; - bs::Address defaultAddr_; - std::vector addresses_; -}; - -class AuthAdressControlProxyModel : public QSortFilterProxyModel { -public: - explicit AuthAdressControlProxyModel(AuthAddressViewModel *sourceModel, QWidget *parent); - ~AuthAdressControlProxyModel() override; - - void setVisibleRowsCount(int rows); - void increaseVisibleRowsCountByOne(); - int getVisibleRowsCount() const; - - void setDefaultAddr(const bs::Address &addr); - bs::Address getAddress(const QModelIndex& index) const; - bool isEmpty() const; - - QModelIndex getFirstUnsubmitted() const; - bool isUnsubmittedAddressVisible() const; - -protected: - bool filterAcceptsRow(int row, const QModelIndex& parent) const override; - -private: - int visibleRowsCount_{}; - QPointer sourceModel_ = nullptr; -}; - -#endif // __AUTH_ADDRESS_MODEL_H__ diff --git a/BlockSettleUILib/BSTerminalMainWindow.cpp b/BlockSettleUILib/BSTerminalMainWindow.cpp deleted file mode 100644 index ec173adf0..000000000 --- a/BlockSettleUILib/BSTerminalMainWindow.cpp +++ /dev/null @@ -1,2481 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "BSTerminalMainWindow.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ArmoryServersProvider.h" -#include "AssetManager.h" -#include "AuthAddressDialog.h" -#include "AuthAddressManager.h" -#include "AutheIDClient.h" -#include "AutoSignQuoteProvider.h" -#include "Bip15xDataConnection.h" -#include "BootstrapDataManager.h" -#include "BSMarketDataProvider.h" -#include "BSMessageBox.h" -#include "BSTerminalSplashScreen.h" -#include "CCFileManager.h" -#include "CCPortfolioModel.h" -#include "CCTokenEntryDialog.h" -#include "CelerAccountInfoDialog.h" -#include "ColoredCoinServer.h" -#include "ConnectionManager.h" -#include "CreatePrimaryWalletPrompt.h" -#include "CreateTransactionDialogAdvanced.h" -#include "CreateTransactionDialogSimple.h" -#include "DialogManager.h" -#include "FutureValue.h" -#include "HeadlessContainer.h" -#include "ImportKeyBox.h" -#include "InfoDialogs/AboutDialog.h" -#include "InfoDialogs/MDAgreementDialog.h" -#include "InfoDialogs/StartupDialog.h" -#include "InfoDialogs/SupportDialog.h" -#include "LoginWindow.h" -#include "MarketDataProvider.h" -#include "MDCallbacksQt.h" -#include "NewAddressDialog.h" -#include "NewWalletDialog.h" -#include "NotificationCenter.h" -#include "OpenURIDialog.h" -#include "OrderListModel.h" -#include "PubKeyLoader.h" -#include "QuoteProvider.h" -#include "RequestReplyCommand.h" -#include "RetryingDataConnection.h" -#include "SelectWalletDialog.h" -#include "Settings/ConfigDialog.h" -#include "SignersProvider.h" -#include "SslCaBundle.h" -#include "SslDataConnection.h" -#include "StatusBarView.h" -#include "StringUtils.h" -#include "SystemFileUtils.h" -#include "TabWithShortcut.h" -#include "TransactionsViewModel.h" -#include "TransactionsWidget.h" -#include "TransportBIP15x.h" -#include "UiUtils.h" -#include "UserScriptRunner.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "WsDataConnection.h" - -#include "ui_BSTerminalMainWindow.h" - -namespace { - const auto kAutoLoginTimer = std::chrono::seconds(10); -} - -BSTerminalMainWindow::BSTerminalMainWindow(const std::shared_ptr& settings - , BSTerminalSplashScreen& splashScreen, QLockFile &lockFile, QWidget* parent) - : QMainWindow(parent) - , ui_(new Ui::BSTerminalMainWindow()) - , applicationSettings_(settings) - , lockFile_(lockFile) -{ - UiUtils::SetupLocale(); - - ui_->setupUi(this); - - setupShortcuts(); - setupInfoWidget(); - - loginButtonText_ = tr("Login"); - - logMgr_ = std::make_shared(); - logMgr_->add(applicationSettings_->GetLogsConfig()); - logMgr_->logger()->debug("Settings loaded from {}", applicationSettings_->GetSettingsPath().toStdString()); - - bool licenseAccepted = showStartupDialog(); - if (!licenseAccepted) { - QMetaObject::invokeMethod(this, []() { - qApp->exit(EXIT_FAILURE); - }, Qt::QueuedConnection); - return; - } - - initBootstrapDataManager(); - - nextArmoryReconnectAttempt_ = std::chrono::steady_clock::now(); - signersProvider_= std::make_shared(applicationSettings_); - armoryServersProvider_ = std::make_shared(applicationSettings_, bootstrapDataManager_); - - if (applicationSettings_->get(ApplicationSettings::armoryDbName).isEmpty()) { - const auto env = static_cast(applicationSettings_->get(ApplicationSettings::envConfiguration)); - switch(env) { - case ApplicationSettings::EnvConfiguration::Production: - armoryServersProvider_->setupServer(armoryServersProvider_->getIndexOfMainNetServer(), false); - break; - case ApplicationSettings::EnvConfiguration::Test: -#ifndef PRODUCTION_BUILD - case ApplicationSettings::EnvConfiguration::Staging: -#endif - armoryServersProvider_->setupServer(armoryServersProvider_->getIndexOfTestNetServer(), false); - break; - } - } - - splashScreen.show(); - - connect(ui_->actionQuit, &QAction::triggered, qApp, &QCoreApplication::quit); - - bs::UtxoReservation::init(logMgr_->logger()); - - setupIcon(); - UiUtils::setupIconFont(this); - NotificationCenter::createInstance(logMgr_->logger(), applicationSettings_, ui_.get(), sysTrayIcon_, this); - - initConnections(); - initArmory(); - initCcClient(); - - walletsMgr_ = std::make_shared(logMgr_->logger(), applicationSettings_, armory_, trackerClient_); - - if (!applicationSettings_->get(ApplicationSettings::initialized)) { - applicationSettings_->SetDefaultSettings(true); - } - - InitAssets(); - InitSigningContainer(); - InitAuthManager(); - initUtxoReservationManager(); - - cbApproveChat_ = PubKeyLoader::getApprovingCallback(PubKeyLoader::KeyType::Chat - , this, applicationSettings_, bootstrapDataManager_); - cbApproveProxy_ = PubKeyLoader::getApprovingCallback(PubKeyLoader::KeyType::Proxy - , this, applicationSettings_, bootstrapDataManager_); - cbApproveCcServer_ = PubKeyLoader::getApprovingCallback(PubKeyLoader::KeyType::CcServer - , this, applicationSettings_, bootstrapDataManager_); - cbApproveExtConn_ = PubKeyLoader::getApprovingCallback(PubKeyLoader::KeyType::ExtConnector - , this, applicationSettings_, bootstrapDataManager_); - - statusBarView_ = std::make_shared(armory_, walletsMgr_, assetManager_, celerConnection_ - , signContainer_, ui_->statusbar); - - splashScreen.SetProgress(100); - splashScreen.close(); - QApplication::processEvents(); - - setupToolbar(); - setupMenu(); - - ui_->widgetTransactions->setEnabled(false); - - connectSigner(); - connectArmory(); - connectCcClient(); - - InitChartsView(); - - ui_->tabWidget->setCurrentIndex(settings->get(ApplicationSettings::GUI_main_tab)); - - UpdateMainWindowAppearence(); - setWidgetsAuthorized(false); - - updateControlEnabledState(); - - InitWidgets(); - - loginApiKeyEncrypted_ = applicationSettings_->get(ApplicationSettings::LoginApiKey); - -#ifdef PRODUCTION_BUILD - const bool showEnvSelector = false; -#else - const bool showEnvSelector = true; -#endif - ui_->prodEnvSettings->setVisible(showEnvSelector); - ui_->testEnvSettings->setVisible(showEnvSelector); -} - -void BSTerminalMainWindow::onBsConnectionDisconnected() -{ - onCelerDisconnected(); -} - -void BSTerminalMainWindow::onBsConnectionFailed() -{ - SPDLOG_LOGGER_ERROR(logMgr_->logger(), "BsClient disconnected unexpectedly"); - showError(tr("Network error"), tr("Connection to BlockSettle server failed")); -} - -void BSTerminalMainWindow::onInitWalletDialogWasShown() -{ - initialWalletCreateDialogShown_ = true; -} - -void BSTerminalMainWindow::setWidgetsAuthorized(bool authorized) -{ - // Update authorized state for some widgets - ui_->widgetPortfolio->setAuthorized(authorized); - ui_->widgetRFQ->setAuthorized(authorized); - ui_->widgetChart->setAuthorized(authorized); -} - -void BSTerminalMainWindow::postSplashscreenActions() -{ - if (applicationSettings_->get(ApplicationSettings::SubscribeToMDOnStart)) { - mdProvider_->SubscribeToMD(); - } -} - -void BSTerminalMainWindow::loadPositionAndShow() -{ - auto geom = applicationSettings_->get(ApplicationSettings::GUI_main_geometry); - if (geom.isEmpty()) { - show(); - return; - } - setGeometry(geom); // This call is required for screenNumber() method to work properly - -#ifdef Q_OS_WINDOWS - int screenNo = QApplication::desktop()->screenNumber(this); - if (screenNo < 0) { - screenNo = 0; - } - const auto screenGeom = QApplication::desktop()->screenGeometry(screenNo); - if (!screenGeom.contains(geom)) { - const int screenWidth = screenGeom.width() * 0.9; - const int screenHeight = screenGeom.height() * 0.9; - geom.setWidth(std::min(geom.width(), screenWidth)); - geom.setHeight(std::min(geom.height(), screenHeight)); - geom.moveCenter(screenGeom.center()); - } - const auto screen = qApp->screens()[screenNo]; - const float pixelRatio = screen->devicePixelRatio(); - if (pixelRatio > 1.0) { - const float coeff = (float)0.9999; // some coefficient that prevents oversizing of main window on HiRes display on Windows - geom.setWidth(geom.width() * coeff); - geom.setHeight(geom.height() * coeff); - } - setGeometry(geom); -#else - if (QApplication::desktop()->screenNumber(this) == -1) { - auto currentScreenRect = QApplication::desktop()->screenGeometry(QCursor::pos()); - // Do not delete 0.9 multiplier, since in some system window size is applying without system native toolbar - geom.setWidth(std::min(geom.width(), static_cast(currentScreenRect.width() * 0.9))); - geom.setHeight(std::min(geom.height(), static_cast(currentScreenRect.height() * 0.9))); - geom.moveCenter(currentScreenRect.center()); - setGeometry(geom); -} -#endif // not Windows - - show(); -} - -bool BSTerminalMainWindow::event(QEvent *event) -{ - if (event->type() == QEvent::WindowActivate) { - auto tabChangedSignal = QMetaMethod::fromSignal(&QTabWidget::currentChanged); - int currentIndex = ui_->tabWidget->currentIndex(); - tabChangedSignal.invoke(ui_->tabWidget, Q_ARG(int, currentIndex)); - } - return QMainWindow::event(event); -} - -BSTerminalMainWindow::~BSTerminalMainWindow() -{ - // Check UTXO reservation state before any other destructors call! - if (bs::UtxoReservation::instance()) { - bs::UtxoReservation::instance()->shutdownCheck(); - } - - applicationSettings_->set(ApplicationSettings::GUI_main_geometry, geometry()); - applicationSettings_->set(ApplicationSettings::GUI_main_tab, ui_->tabWidget->currentIndex()); - applicationSettings_->SaveSettings(); - - NotificationCenter::destroyInstance(); - if (signContainer_) { - signContainer_->Stop(); - signContainer_.reset(); - } - walletsMgr_.reset(); - assetManager_.reset(); -} - -void BSTerminalMainWindow::setupToolbar() -{ - action_send_ = new QAction(tr("Send Bitcoin"), this); - connect(action_send_, &QAction::triggered, this, &BSTerminalMainWindow::onSend); - - action_generate_address_ = new QAction(tr("Generate &Address"), this); - connect(action_generate_address_, &QAction::triggered, this, &BSTerminalMainWindow::onGenerateAddress); - - action_login_ = new QAction(tr("Login to BlockSettle"), this); - connect(action_login_, &QAction::triggered, this, &BSTerminalMainWindow::onLogin); - - action_logout_ = new QAction(tr("Logout from BlockSettle"), this); - connect(action_logout_, &QAction::triggered, this, &BSTerminalMainWindow::onLogout); - - setupTopRightWidget(); - - action_logout_->setVisible(false); - - connect(ui_->pushButtonUser, &QPushButton::clicked, this, &BSTerminalMainWindow::onButtonUserClicked); - - QMenu* trayMenu = new QMenu(this); - QAction* trayShowAction = trayMenu->addAction(tr("&Open Terminal")); - connect(trayShowAction, &QAction::triggered, this, &QMainWindow::show); - trayMenu->addSeparator(); - - trayMenu->addAction(action_send_); - trayMenu->addAction(action_generate_address_); - trayMenu->addAction(ui_->actionSettings); - - trayMenu->addSeparator(); - trayMenu->addAction(ui_->actionQuit); - sysTrayIcon_->setContextMenu(trayMenu); -} - -void BSTerminalMainWindow::setupTopRightWidget() -{ - auto toolBar = new QToolBar(this); - toolBar->setObjectName(QLatin1String("mainToolBar")); - toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - ui_->tabWidget->setCornerWidget(toolBar, Qt::TopRightCorner); - - toolBar->addAction(action_send_); - toolBar->addAction(action_generate_address_); - - for (int i = 0; i < toolBar->children().size(); ++i) { - auto *toolButton = qobject_cast(toolBar->children().at(i)); - if (toolButton && (toolButton->defaultAction() == action_send_ - || toolButton->defaultAction() == action_generate_address_)) { - toolButton->setObjectName(QLatin1String("mainToolBarActions")); - } - } - -#if defined(Q_OS_WIN) - ui_->tabWidget->setProperty("onWindows", QVariant(true)); -#elif defined(Q_OS_LINUX) - ui_->tabWidget->setProperty("onLinux", QVariant(true)); -#else - ui_->tabWidget->setProperty("onMacos", QVariant(true)); -#endif - - auto *prevStyle = ui_->tabWidget->style(); - ui_->tabWidget->setStyle(nullptr); - ui_->tabWidget->setStyle(prevStyle); -} - -void BSTerminalMainWindow::setupIcon() -{ - QIcon icon; - QString iconFormatString = QString::fromStdString(":/ICON_BS_%1"); - - for (const int s : {16, 24, 32}) { - icon.addFile(iconFormatString.arg(s), QSize(s, s)); - } - - setWindowIcon(icon); - - sysTrayIcon_ = std::make_shared(icon, this); - sysTrayIcon_->setToolTip(windowTitle()); - sysTrayIcon_->show(); - - connect(sysTrayIcon_.get(), &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) { - if (reason == QSystemTrayIcon::Context) { - // Right click, this is handled by the menu, so we don't do anything here. - return; - } - - setWindowState(windowState() & ~Qt::WindowMinimized); - show(); - raise(); - activateWindow(); - }); - - connect(qApp, &QCoreApplication::aboutToQuit, sysTrayIcon_.get(), &QSystemTrayIcon::hide); - connect(qApp, SIGNAL(lastWindowClosed()), sysTrayIcon_.get(), SLOT(hide())); -} - -void BSTerminalMainWindow::setupInfoWidget() -{ - const bool show = applicationSettings_->get(ApplicationSettings::ShowInfoWidget); - ui_->infoWidget->setVisible(show); - - if (!show) { - return; - } - - connect(ui_->introductionBtn, &QPushButton::clicked, this, []() { - QDesktopServices::openUrl(QUrl(QLatin1String("https://www.youtube.com/watch?v=mUqKq9GKjmI"))); - }); - connect(ui_->tutorialsButton, &QPushButton::clicked, this, []() { - QDesktopServices::openUrl(QUrl(QLatin1String("https://blocksettle.com/tutorials"))); - }); - connect(ui_->closeBtn, &QPushButton::clicked, this, [this]() { - ui_->infoWidget->setVisible(false); - applicationSettings_->set(ApplicationSettings::ShowInfoWidget, false); - }); -} - -void BSTerminalMainWindow::initConnections() -{ - connectionManager_ = std::make_shared(logMgr_->logger("message")); - connectionManager_->setCaBundle(bs::caBundlePtr(), bs::caBundleSize()); - - celerConnection_ = std::make_shared(logMgr_->logger()); - connect(celerConnection_.get(), &BaseCelerClient::OnConnectedToServer, this, &BSTerminalMainWindow::onCelerConnected); - connect(celerConnection_.get(), &BaseCelerClient::OnConnectionClosed, this, &BSTerminalMainWindow::onCelerDisconnected); - connect(celerConnection_.get(), &BaseCelerClient::OnConnectionError, this, &BSTerminalMainWindow::onCelerConnectionError, Qt::QueuedConnection); - - mdCallbacks_ = std::make_shared(); - mdProvider_ = std::make_shared(connectionManager_ - , logMgr_->logger("message"), mdCallbacks_.get(), true, false); - connect(mdCallbacks_.get(), &MDCallbacksQt::UserWantToConnectToMD, this, &BSTerminalMainWindow::acceptMDAgreement); - connect(mdCallbacks_.get(), &MDCallbacksQt::WaitingForConnectionDetails, this, [this] { - auto env = static_cast( - applicationSettings_->get(ApplicationSettings::envConfiguration)); - mdProvider_->SetConnectionSettings(PubKeyLoader::serverHostName(PubKeyLoader::KeyType::MdServer, env) - , PubKeyLoader::serverHttpsPort()); - }); -} - -void BSTerminalMainWindow::LoadWallets() -{ - logMgr_->logger()->debug("Loading wallets"); - - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletsReady, this, [this] { - ui_->widgetRFQ->setWalletsManager(walletsMgr_); - ui_->widgetRFQReply->setWalletsManager(walletsMgr_); - autoSignQuoteProvider_->setWalletsManager(walletsMgr_); - autoSignRFQProvider_->setWalletsManager(walletsMgr_); - }); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletsSynchronized, this, [this] { - walletsSynched_ = true; - updateControlEnabledState(); - CompleteDBConnection(); - act_->onRefresh({}, true); - tryGetChatKeys(); - }); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::info, this, &BSTerminalMainWindow::showInfo); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::error, this, &BSTerminalMainWindow::showError); - - // Enable/disable send action when first wallet created/last wallet removed - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletChanged, this - , &BSTerminalMainWindow::updateControlEnabledState); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletDeleted, this, [this] { - updateControlEnabledState(); - resetChatKeys(); - }); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletAdded, this, [this] { - updateControlEnabledState(); - tryGetChatKeys(); - }); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::newWalletAdded, this - , &BSTerminalMainWindow::updateControlEnabledState); - - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, [this](const std::string &walletId) { - auto wallet = dynamic_cast(walletsMgr_->getWalletById(walletId).get()); - if (wallet && wallet->purpose() == bs::hd::Purpose::NonSegWit && wallet->getTotalBalance() > 0) { - showLegacyWarningIfNeeded(); - } - }); - - connect(walletsMgr_.get(), &bs::sync::WalletsManager::AuthLeafCreated, this, &BSTerminalMainWindow::onAuthLeafCreated); - - onSyncWallets(); -} - -void BSTerminalMainWindow::InitAuthManager() -{ - authManager_ = std::make_shared(logMgr_->logger(), armory_); - authManager_->init(applicationSettings_, walletsMgr_, signContainer_); - - connect(authManager_.get(), &AuthAddressManager::AddrVerifiedOrRevoked, this, [](const QString &addr, const QString &state) { - NotificationCenter::notify(bs::ui::NotifyType::AuthAddress, { addr, state }); - }); - connect(authManager_.get(), &AuthAddressManager::AuthWalletCreated, this, [this](const QString &walletId) { - if (authAddrDlg_ && walletId.isEmpty()) { - openAuthManagerDialog(); - } - }); - - authManager_->SetLoadedValidationAddressList(bootstrapDataManager_->GetAuthValidationList()); -} - -std::shared_ptr BSTerminalMainWindow::createSigner() -{ - if (signersProvider_->currentSignerIsLocal()) { - return createLocalSigner(); - } else { - return createRemoteSigner(); - } -} - -std::shared_ptr BSTerminalMainWindow::createRemoteSigner(bool restoreHeadless) -{ - SignerHost signerHost = signersProvider_->getCurrentSigner(); - QString resultPort = QString::number(signerHost.port); - NetworkType netType = applicationSettings_->get(ApplicationSettings::netType); - - // Define the callback that will be used to determine if the signer's BIP - // 150 identity key, if it has changed, will be accepted. It needs strings - // for the old and new keys, and a promise to set once the user decides. - const auto &ourNewKeyCB = [this](const std::string& oldKey, const std::string& newKey - , const std::string& srvAddrPort - , const std::shared_ptr> &newKeyProm) { - logMgr_->logger()->debug("[BSTerminalMainWindow::createSigner::callback] received" - " new key {} [{}], old key {} [{}] for {} ({})", newKey, newKey.size(), oldKey - , oldKey.size(), srvAddrPort, signersProvider_->getCurrentSigner().serverId()); - std::string oldKeyHex = oldKey; - if (oldKeyHex.empty() && (signersProvider_->getCurrentSigner().serverId() == srvAddrPort)) { - oldKeyHex = signersProvider_->getCurrentSigner().key.toStdString(); - } - - const auto &deferredDialog = [this, oldKeyHex, newKey, newKeyProm, srvAddrPort]{ - ImportKeyBox box(BSMessageBox::question - , tr("Import Signer ID Key?") - , this); - - box.setNewKey(newKey); - box.setOldKey(QString::fromStdString(oldKeyHex)); - box.setAddrPort(srvAddrPort); - - const bool answer = (box.exec() == QDialog::Accepted); - - if (answer) { - signersProvider_->addKey(srvAddrPort, newKey); - } - - bool result = newKeyProm->setValue(answer); - if (!result) { - SPDLOG_LOGGER_DEBUG(logMgr_->logger() - , "can't set result for signer key prompt for {}, perhaps connection was already closed" - , srvAddrPort); - } - }; - - addDeferredDialog(deferredDialog); - }; - - QString resultHost = signerHost.address; - const auto remoteSigner = std::make_shared(logMgr_->logger() - , resultHost, resultPort, netType, connectionManager_ - , SignContainer::OpMode::Remote, false - , signersProvider_->remoteSignerKeysDir(), signersProvider_->remoteSignerKeysFile(), ourNewKeyCB); - - bs::network::BIP15xPeers peers; - for (const auto &signer : signersProvider_->signers()) { - try { - const BinaryData signerKey = BinaryData::CreateFromHex(signer.key.toStdString()); - peers.push_back(bs::network::BIP15xPeer(signer.serverId(), signerKey)); - } - catch (const std::exception &e) { - logMgr_->logger()->warn("[{}] invalid signer key: {}", __func__, e.what()); - } - } - remoteSigner->updatePeerKeys(peers); - - if (restoreHeadless) { - // setup headleass signer back (it was changed when createLocalSigner called signersProvider_->switchToLocalFullGUI) - signersProvider_->setupSigner(0, true); - } - - return remoteSigner; -} - -std::shared_ptr BSTerminalMainWindow::createLocalSigner() -{ - QLatin1String localSignerHost("127.0.0.1"); - QString localSignerPort; - NetworkType netType = applicationSettings_->get(ApplicationSettings::netType); - - for (int attempts = 0; attempts < 10; ++attempts) { - // https://tools.ietf.org/html/rfc6335 - // the Dynamic Ports, also known as the Private or Ephemeral Ports, - // from 49152-65535 (never assigned) - auto port = 49152 + rand() % 16000; - - auto portToTest = QString::number(port); - - if (!SignerConnectionExists(localSignerHost, portToTest)) { - localSignerPort = portToTest; - break; - } else { - logMgr_->logger()->error("[BSTerminalMainWindow::createLocalSigner] attempt {} : port {} used" - , port); - } - } - - if (localSignerPort.isEmpty()) { - logMgr_->logger()->error("[BSTerminalMainWindow::createLocalSigner] failed to find not busy port"); - return nullptr; - } - - const bool startLocalSignerProcess = true; - return std::make_shared(logMgr_->logger() - , applicationSettings_->GetHomeDir(), netType - , localSignerPort, connectionManager_ - , startLocalSignerProcess, "", "" - , applicationSettings_->get(ApplicationSettings::autoSignSpendLimit)); -} - -bool BSTerminalMainWindow::InitSigningContainer() -{ - signContainer_ = createSigner(); - - if (!signContainer_) { - showError(tr("BlockSettle Signer"), tr("BlockSettle Signer creation failure")); - return false; - } - connect(signContainer_.get(), &SignContainer::connectionError, this, &BSTerminalMainWindow::onSignerConnError, Qt::QueuedConnection); - connect(signContainer_.get(), &SignContainer::disconnected, this, &BSTerminalMainWindow::updateControlEnabledState, Qt::QueuedConnection); - - walletsMgr_->setSignContainer(signContainer_); - connect(signContainer_.get(), &WalletSignerContainer::ready, this, &BSTerminalMainWindow::SignerReady, Qt::QueuedConnection); - connect(signContainer_.get(), &WalletSignerContainer::needNewWalletPrompt, this, &BSTerminalMainWindow::onNeedNewWallet, Qt::QueuedConnection); - connect(signContainer_.get(), &WalletSignerContainer::walletsReadyToSync, this, &BSTerminalMainWindow::onSyncWallets, Qt::QueuedConnection); - connect(signContainer_.get(), &WalletSignerContainer::windowVisibilityChanged, this, &BSTerminalMainWindow::onSignerVisibleChanged, Qt::QueuedConnection); - - return true; -} - -void BSTerminalMainWindow::SignerReady() -{ - updateControlEnabledState(); - - LoadWallets(); - - walletsMgr_->setUserId(BinaryData::CreateFromHex(celerConnection_->userId())); - - if (deferCCsync_) { - signContainer_->syncCCNames(walletsMgr_->ccResolver()->securities()); - deferCCsync_ = false; - } - - lastSignerError_ = SignContainer::NoError; -} - -void BSTerminalMainWindow::onNeedNewWallet() -{ - if (!initialWalletCreateDialogShown_) { - initialWalletCreateDialogShown_ = true; - const auto &deferredDialog = [this]{ - ui_->widgetWallets->onNewWallet(); - }; - addDeferredDialog(deferredDialog); - } -} - -void BSTerminalMainWindow::acceptMDAgreement() -{ - const auto &deferredDailog = [this]{ - if (!isMDLicenseAccepted()) { - MDAgreementDialog dlg{this}; - if (dlg.exec() != QDialog::Accepted) { - return; - } - - saveUserAcceptedMDLicense(); - } - - mdProvider_->MDLicenseAccepted(); - }; - - addDeferredDialog(deferredDailog); -} - -void BSTerminalMainWindow::updateControlEnabledState() -{ - if (action_send_) { - const bool txCreationEnabled = !walletsMgr_->hdWallets().empty() - && armory_->isOnline() && signContainer_ && signContainer_->isReady(); - - action_send_->setEnabled(txCreationEnabled); - ui_->actionOpenURI->setEnabled(txCreationEnabled); - } - // Do not allow login until wallets synced (we need to check if user has primary wallet or not). - // Should be OK for both local and remote signer. - bool loginAllowed = walletsSynched_ && loginApiKeyEncrypted().empty(); - ui_->pushButtonUser->setEnabled(loginAllowed); - action_login_->setEnabled(true); - - action_login_->setVisible(!celerConnection_->IsConnected()); - action_login_->setEnabled(loginAllowed); - action_logout_->setVisible(celerConnection_->IsConnected()); -} - -bool BSTerminalMainWindow::isMDLicenseAccepted() const -{ - return applicationSettings_->get(ApplicationSettings::MDLicenseAccepted); -} - -void BSTerminalMainWindow::saveUserAcceptedMDLicense() -{ - applicationSettings_->set(ApplicationSettings::MDLicenseAccepted, true); -} - -bool BSTerminalMainWindow::showStartupDialog() -{ - bool wasInitialized = applicationSettings_->get(ApplicationSettings::initialized); - if (wasInitialized) { - return true; - } - - #ifdef _WIN32 - // Read registry value in case it was set with installer. Could be used only on Windows for now. - QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\blocksettle\\blocksettle"), QSettings::NativeFormat); - bool showLicense = !settings.value(QLatin1String("license_accepted"), false).toBool(); - #else - bool showLicense = true; - #endif // _WIN32 - - StartupDialog startupDialog(showLicense); - startupDialog.init(applicationSettings_); - int result = startupDialog.exec(); - - if (result == QDialog::Rejected) { - hide(); - return false; - } - - // Need update armory settings if case user selects TestNet - startupDialog.applySelectedConnectivity(); - - return true; -} - -void BSTerminalMainWindow::InitAssets() -{ - ccFileManager_ = std::make_shared(logMgr_->logger(), applicationSettings_); - assetManager_ = std::make_shared(logMgr_->logger(), walletsMgr_ - , mdCallbacks_, celerConnection_); - assetManager_->init(); - - orderListModel_ = std::make_unique(assetManager_); - - connect(ccFileManager_.get(), &CCFileManager::CCSecurityDef, assetManager_.get(), &AssetManager::onCCSecurityReceived); - connect(ccFileManager_.get(), &CCFileManager::CCSecurityInfo, walletsMgr_.get(), &bs::sync::WalletsManager::onCCSecurityInfo); - connect(ccFileManager_.get(), &CCFileManager::Loaded, this, &BSTerminalMainWindow::onCCLoaded); - - connect(mdCallbacks_.get(), &MDCallbacksQt::MDUpdate, assetManager_.get(), &AssetManager::onMDUpdate); - - ccFileManager_->SetLoadedDefinitions(bootstrapDataManager_->GetCCDefinitions()); -} - -void BSTerminalMainWindow::InitPortfolioView() -{ - portfolioModel_ = std::make_shared(walletsMgr_, assetManager_, this); - ui_->widgetPortfolio->init(applicationSettings_, mdProvider_, mdCallbacks_ - , portfolioModel_, signContainer_, armory_, utxoReservationMgr_, logMgr_->logger("ui"), walletsMgr_); -} - -void BSTerminalMainWindow::InitWalletsView() -{ - ui_->widgetWallets->init(logMgr_->logger("ui"), walletsMgr_, signContainer_ - , applicationSettings_, connectionManager_, assetManager_, authManager_, armory_); - connect(ui_->widgetWallets, &WalletsWidget::newWalletCreationRequest, this, &BSTerminalMainWindow::onInitWalletDialogWasShown); -} - -void BSTerminalMainWindow::tryInitChatView() -{ - // Chat initialization is a bit convoluted. - // First we need to create and initialize chatClientServicePtr_ (which lives in background thread and so is async). - // For this it needs to know chat server address where to connect and chat keys used for chat messages encryption. - // Only after that we could init ui_->widgetChat and try to login after that. - if (chatInitState_ != ChatInitState::NoStarted || !gotChatKeys_) { - return; - } - chatInitState_ = ChatInitState::InProgress; - - chatClientServicePtr_ = std::make_shared(); - - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::initDone, this, [this]() { - const bool isProd = applicationSettings_->get(ApplicationSettings::envConfiguration) == - static_cast(ApplicationSettings::EnvConfiguration::Production); - const auto env = isProd ? bs::network::otc::Env::Prod : bs::network::otc::Env::Test; - - ui_->widgetChat->init(connectionManager_, env, chatClientServicePtr_, - logMgr_->logger("chat"), walletsMgr_, authManager_, armory_, signContainer_, - mdCallbacks_, assetManager_, utxoReservationMgr_, applicationSettings_); - - connect(chatClientServicePtr_->getClientPartyModelPtr().get(), &Chat::ClientPartyModel::userPublicKeyChanged, - this, [this](const Chat::UserPublicKeyInfoList& userPublicKeyInfoList) { - addDeferredDialog([this, userPublicKeyList = userPublicKeyInfoList]() { - ui_->widgetChat->onUserPublicKeyChanged(userPublicKeyList); - }); - }, Qt::QueuedConnection); - - chatInitState_ = ChatInitState::Done; - tryLoginIntoChat(); - }); - - auto env = static_cast( - applicationSettings_->get(ApplicationSettings::envConfiguration)); - - Chat::ChatSettings chatSettings; - chatSettings.connectionManager = connectionManager_; - chatSettings.chatPrivKey = chatPrivKey_; - chatSettings.chatPubKey = chatPubKey_; - chatSettings.chatServerHost = PubKeyLoader::serverHostName(PubKeyLoader::KeyType::Chat, env); - chatSettings.chatServerPort = PubKeyLoader::serverHttpPort(); - chatSettings.chatDbFile = applicationSettings_->get(ApplicationSettings::chatDbFile); - - chatClientServicePtr_->Init(logMgr_->logger("chat"), chatSettings); - - connect(ui_->tabWidget, &QTabWidget::currentChanged, this, &BSTerminalMainWindow::onTabWidgetCurrentChanged); - connect(ui_->widgetChat, &ChatWidget::requestPrimaryWalletCreation, this, &BSTerminalMainWindow::onCreatePrimaryWalletRequest); - - if (NotificationCenter::instance() != nullptr) { - connect(NotificationCenter::instance(), &NotificationCenter::newChatMessageClick, ui_->widgetChat, &ChatWidget::onNewChatMessageTrayNotificationClicked); - } -} - -void BSTerminalMainWindow::tryLoginIntoChat() -{ - if (chatInitState_ != ChatInitState::Done || chatTokenData_.empty() || chatTokenSign_.empty()) { - return; - } - - chatClientServicePtr_->LoginToServer(chatTokenData_, chatTokenSign_, cbApproveChat_); - - chatTokenData_.clear(); - chatTokenSign_.clear(); -} - -void BSTerminalMainWindow::resetChatKeys() -{ - gotChatKeys_ = false; - chatPubKey_.clear(); - chatPrivKey_.clear(); - tryGetChatKeys(); -} - -void BSTerminalMainWindow::tryGetChatKeys() -{ - if (gotChatKeys_) { - return; - } - const auto &primaryWallet = walletsMgr_->getPrimaryWallet(); - if (!primaryWallet) { - // Reset API key if it was stored (as it won't be possible to decrypt it) - applicationSettings_->reset(ApplicationSettings::LoginApiKey); - loginApiKeyEncrypted_.clear(); - return; - } - signContainer_->getChatNode(primaryWallet->walletId(), [this](const BIP32_Node &node) { - if (node.getPublicKey().empty() || node.getPrivateKey().empty()) { - SPDLOG_LOGGER_ERROR(logMgr_->logger(), "chat keys is empty"); - return; - } - chatPubKey_ = node.getPublicKey(); - chatPrivKey_ = node.getPrivateKey(); - gotChatKeys_ = true; - tryInitChatView(); - initApiKeyLogins(); - }); -} - -void BSTerminalMainWindow::InitChartsView() -{ - ui_->widgetChart->init(applicationSettings_, mdProvider_, mdCallbacks_ - , connectionManager_, logMgr_->logger("ui")); -} - -// Initialize widgets related to transactions. -void BSTerminalMainWindow::InitTransactionsView() -{ - ui_->widgetExplorer->init(armory_, logMgr_->logger(), walletsMgr_, ccFileManager_, authManager_); - ui_->widgetTransactions->init(walletsMgr_, armory_, utxoReservationMgr_, signContainer_, applicationSettings_ - , logMgr_->logger("ui")); - ui_->widgetTransactions->setEnabled(true); - - ui_->widgetTransactions->SetTransactionsModel(transactionsModel_); - ui_->widgetPortfolio->SetTransactionsModel(transactionsModel_); -} - -void BSTerminalMainWindow::MainWinACT::onStateChanged(ArmoryState state) -{ - switch (state) { - case ArmoryState::Ready: - QMetaObject::invokeMethod(parent_, [this] { - parent_->isArmoryReady_ = true; - parent_->armoryReconnectDelay_ = 0; - parent_->CompleteDBConnection(); - parent_->CompleteUIOnlineView(); - parent_->walletsMgr_->goOnline(); - - parent_->armory_->getNodeStatus([this] (const std::shared_ptr &status){ - QMetaObject::invokeMethod(parent_, [this, status] { - if (status) { - parent_->onNodeStatus(status->status(), status->isSegWitEnabled(), status->rpcStatus()); - } - }); - }); - }); - break; - case ArmoryState::Connected: - QMetaObject::invokeMethod(parent_, [this] { - parent_->armoryRestartCount_ = 0; - parent_->wasWalletsRegistered_ = false; - parent_->armory_->goOnline(); - }); - break; - case ArmoryState::Offline: - QMetaObject::invokeMethod(parent_, &BSTerminalMainWindow::ArmoryIsOffline); - break; - default: - break; - } -} - -void BSTerminalMainWindow::CompleteUIOnlineView() -{ - if (!transactionsModel_) { - transactionsModel_ = std::make_shared(armory_ - , walletsMgr_, logMgr_->logger("ui"), this); - - InitTransactionsView(); - transactionsModel_->loadAllWallets(); - } - updateControlEnabledState(); -} - -void BSTerminalMainWindow::CompleteDBConnection() -{ - if (!wasWalletsRegistered_ && walletsSynched_ && isArmoryReady_) { - // Fix race with BDMAction_Refresh and BDMAction_Ready: register wallets AFTER armory becames ready. - // Otherwise BDMAction_Refresh might come before BDMAction_Ready causing a lot of problems. - walletsMgr_->registerWallets(); - wasWalletsRegistered_ = true; - } -} - -void BSTerminalMainWindow::onReactivate() -{ - show(); -} - -void BSTerminalMainWindow::raiseWindow() -{ - if (isMinimized()) { - showNormal(); - } else if (isHidden()) { - show(); - } - raise(); - activateWindow(); - setFocus(); -#ifdef Q_OS_WIN - auto hwnd = reinterpret_cast(winId()); - auto flags = static_cast(SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); - auto currentProcessId = ::GetCurrentProcessId(); - auto currentThreadId = ::GetCurrentThreadId(); - auto windowThreadId = ::GetWindowThreadProcessId(hwnd, nullptr); - if (currentThreadId != windowThreadId) { - ::AttachThreadInput(windowThreadId, currentThreadId, TRUE); - } - ::AllowSetForegroundWindow(currentProcessId); - ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, flags); - ::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, flags); - ::SetForegroundWindow(hwnd); - ::SetFocus(hwnd); - ::SetActiveWindow(hwnd); - if (currentThreadId != windowThreadId) { - ::AttachThreadInput(windowThreadId, currentThreadId, FALSE); - } -#endif // Q_OS_WIN -} - -void BSTerminalMainWindow::UpdateMainWindowAppearence() -{ - if (!applicationSettings_->get(ApplicationSettings::closeToTray) && isHidden()) { - setWindowState(windowState() & ~Qt::WindowMinimized); - show(); - raise(); - activateWindow(); - } - - setWindowTitle(tr("BlockSettle Terminal")); - -// const auto bsTitle = tr("BlockSettle Terminal [%1]"); -// switch (applicationSettings_->get(ApplicationSettings::netType)) { -// case NetworkType::TestNet: -// setWindowTitle(bsTitle.arg(tr("TESTNET"))); -// break; - -// case NetworkType::RegTest: -// setWindowTitle(bsTitle.arg(tr("REGTEST"))); -// break; - -// default: -// setWindowTitle(tr("BlockSettle Terminal")); -// break; -// } -} - -bool BSTerminalMainWindow::isUserLoggedIn() const -{ - return (celerConnection_ && celerConnection_->IsConnected()); -} - -bool BSTerminalMainWindow::isArmoryConnected() const -{ - return armory_->state() == ArmoryState::Ready; -} - -void BSTerminalMainWindow::ArmoryIsOffline() -{ - logMgr_->logger("ui")->debug("BSTerminalMainWindow::ArmoryIsOffline"); - - auto now = std::chrono::steady_clock::now(); - std::chrono::milliseconds nextConnDelay(0); - if (now < nextArmoryReconnectAttempt_) { - nextConnDelay = std::chrono::duration_cast( - nextArmoryReconnectAttempt_ - now); - } - if (nextConnDelay != std::chrono::milliseconds::zero()) { - - auto delaySec = std::chrono::duration_cast(nextConnDelay); - SPDLOG_LOGGER_DEBUG(logMgr_->logger("ui") - , "restart armory connection in {} second", nextConnDelay.count()); - - QTimer::singleShot(nextConnDelay, this, &BSTerminalMainWindow::ArmoryIsOffline); - return; - } - - if (walletsMgr_) { - walletsMgr_->unregisterWallets(); - } - updateControlEnabledState(); - - //increase reconnect delay - armoryReconnectDelay_ = armoryReconnectDelay_ % 2 ? - armoryReconnectDelay_ * 2 : armoryReconnectDelay_ + 1; - armoryReconnectDelay_ = std::max(armoryReconnectDelay_, unsigned(60)); - nextArmoryReconnectAttempt_ = - std::chrono::steady_clock::now() + std::chrono::seconds(armoryReconnectDelay_); - - connectArmory(); - - // XXX: disabled until armory connection is stable in terminal - // updateLoginActionState(); -} - -void BSTerminalMainWindow::initArmory() -{ - armory_ = std::make_shared(logMgr_->logger() - , applicationSettings_->get(ApplicationSettings::txCacheFileName), true); - act_ = make_unique(this); - act_->init(armory_.get()); -} - -void BSTerminalMainWindow::initCcClient() -{ - bool isDefaultArmory = armoryServersProvider_->isDefault(armoryServersProvider_->indexOfCurrent()); - if (isDefaultArmory) { - trackerClient_ = std::make_shared(logMgr_->logger()); - } -} - -void BSTerminalMainWindow::initUtxoReservationManager() -{ - utxoReservationMgr_ = std::make_shared( - walletsMgr_, armory_, logMgr_->logger()); -} - -void BSTerminalMainWindow::initBootstrapDataManager() -{ - bootstrapDataManager_ = std::make_shared(logMgr_->logger(), applicationSettings_); - if (bootstrapDataManager_->hasLocalFile()) { - bootstrapDataManager_->loadFromLocalFile(); - } else { - // load from resources - const QString filePathInResources = applicationSettings_->bootstrapResourceFileName(); - - QFile file; - file.setFileName(filePathInResources); - if (file.open(QIODevice::ReadOnly)) { - const std::string bootstrapData = file.readAll().toStdString(); - - bootstrapDataManager_->setReceivedData(bootstrapData); - } else { - logMgr_->logger()->error("[BSTerminalMainWindow::initBootstrapDataManager] failed to locate bootstrap file in resources: {}" - , filePathInResources.toStdString()); - } - - } -} - -void BSTerminalMainWindow::MainWinACT::onTxBroadcastError(const std::string& requestId, const BinaryData &txHash, int errCode - , const std::string &errMsg) -{ - NotificationCenter::notify(bs::ui::NotifyType::BroadcastError, - { QString::fromStdString(txHash.toHexStr(true)), QString::fromStdString(errMsg) }); -} - -void BSTerminalMainWindow::MainWinACT::onNodeStatus(NodeStatus nodeStatus, bool isSegWitEnabled, RpcStatus rpcStatus) -{ - QMetaObject::invokeMethod(parent_, [parent = parent_, nodeStatus, isSegWitEnabled, rpcStatus] { - parent->onNodeStatus(nodeStatus, isSegWitEnabled, rpcStatus); - }); -} - -void BSTerminalMainWindow::MainWinACT::onZCReceived(const std::string& requestId, const std::vector& zcs) -{ - QMetaObject::invokeMethod(parent_, [this, zcs] { parent_->onZCreceived(zcs); }); -} - -void BSTerminalMainWindow::connectArmory() -{ - ArmorySettings currentArmorySettings = armoryServersProvider_->getArmorySettings(); - armoryServersProvider_->setConnectedArmorySettings(currentArmorySettings); - armory_->setupConnection(currentArmorySettings, [this](const BinaryData& srvPubKey, const std::string& srvIPPort) { - auto promiseObj = std::make_shared>(); - std::future futureObj = promiseObj->get_future(); - QMetaObject::invokeMethod(this, [this, srvPubKey, srvIPPort, promiseObj] { - showArmoryServerPrompt(srvPubKey, srvIPPort, promiseObj); - }); - - bool result = futureObj.get(); - // stop armory connection loop if server key was rejected - if (!result) { - armory_->needsBreakConnectionLoop_.store(true); - armory_->setState(ArmoryState::Cancelled); - } - return result; - }); -} - -void BSTerminalMainWindow::connectCcClient() -{ - if (trackerClient_) { - auto env = static_cast( - applicationSettings_->get(ApplicationSettings::envConfiguration)); - auto trackerHostName = PubKeyLoader::serverHostName(PubKeyLoader::KeyType::CcServer, env); - trackerClient_->openConnection(trackerHostName, PubKeyLoader::serverHttpPort(), cbApproveCcServer_); - } -} - -void BSTerminalMainWindow::connectSigner() -{ - if (!signContainer_) { - return; - } - - signContainer_->Start(); -} - -bool BSTerminalMainWindow::createPrimaryWallet() -{ - auto primaryWallet = walletsMgr_->getPrimaryWallet(); - if (primaryWallet) { - return true; - } - - - for (const auto &wallet : walletsMgr_->hdWallets()) { - if (!wallet->isOffline() && !wallet->isHardwareWallet()) { - BSMessageBox qry(BSMessageBox::question, tr("Promote to primary wallet"), tr("Promote to primary wallet?") - , tr("To trade through BlockSettle, you are required to have a wallet which" - " supports the sub-wallets required to interact with the system. Each Terminal" - " may only have one Primary Wallet. Do you wish to promote '%1'?") - .arg(QString::fromStdString(wallet->name())), this); - if (qry.exec() == QDialog::Accepted) { - walletsMgr_->PromoteWalletToPrimary(wallet->walletId()); - return true; - } - } - } - - CreatePrimaryWalletPrompt dlg; - int rc = dlg.exec(); - if (rc == CreatePrimaryWalletPrompt::CreateWallet) { - ui_->widgetWallets->CreateNewWallet(); - } else if (rc == CreatePrimaryWalletPrompt::ImportWallet) { - ui_->widgetWallets->ImportNewWallet(); - } - - return true; -} - -void BSTerminalMainWindow::onCreatePrimaryWalletRequest() -{ - bool result = createPrimaryWallet(); - - if (!result) { - // Need to inform UI about rejection - ui_->widgetRFQ->forceCheckCondition(); - ui_->widgetRFQReply->forceCheckCondition(); - ui_->widgetChat->onUpdateOTCShield(); - } -} - -void BSTerminalMainWindow::showInfo(const QString &title, const QString &text) -{ - BSMessageBox(BSMessageBox::info, title, text).exec(); -} - -void BSTerminalMainWindow::showError(const QString &title, const QString &text) -{ - QMetaObject::invokeMethod(this, [this, title, text] { - BSMessageBox(BSMessageBox::critical, title, text, this).exec(); - }); -} - -void BSTerminalMainWindow::onSignerConnError(SignContainer::ConnectionError error, const QString &details) -{ - updateControlEnabledState(); - - // Prevent showing multiple signer error dialogs (for example network mismatch) - if (error == lastSignerError_) { - return; - } - lastSignerError_ = error; - - if (error != SignContainer::ConnectionTimeout || signContainer_->isLocal()) { - showError(tr("Signer Connection Error"), details); - } -} - -void BSTerminalMainWindow::onGenerateAddress() -{ - if (walletsMgr_->hdWallets().empty()) { - createPrimaryWallet(); - return; - } - - const auto defWallet = walletsMgr_->getDefaultWallet(); - std::string selWalletId = defWallet ? defWallet->walletId() : std::string{}; - - if (ui_->tabWidget->currentWidget() == ui_->widgetWallets) { - auto wallets = ui_->widgetWallets->getSelectedWallets(); - if (!wallets.empty()) { - selWalletId = wallets[0]->walletId(); - } else { - wallets = ui_->widgetWallets->getFirstWallets(); - - if (!wallets.empty()) { - selWalletId = wallets[0]->walletId(); - } - } - } - SelectWalletDialog selectWalletDialog(walletsMgr_, selWalletId, this); - selectWalletDialog.exec(); - - if (selectWalletDialog.result() == QDialog::Rejected) { - return; - } - - NewAddressDialog newAddressDialog(selectWalletDialog.getSelectedWallet(), this); - newAddressDialog.exec(); -} - -void BSTerminalMainWindow::onSend() -{ - std::string selectedWalletId; - - if (ui_->tabWidget->currentWidget() == ui_->widgetWallets) { - auto wallet = ui_->widgetWallets->getSelectedHdWallet(); - if (!wallet) { - wallet = walletsMgr_->getPrimaryWallet(); - } - if (wallet) { - selectedWalletId = wallet->walletId(); - } - } else { - selectedWalletId = applicationSettings_->getDefaultWalletId(); - } - - - std::shared_ptr dlg; - - if ((QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) - || applicationSettings_->get(ApplicationSettings::AdvancedTxDialogByDefault)) { - dlg = std::make_shared(armory_, walletsMgr_, utxoReservationMgr_ - , signContainer_, true, logMgr_->logger("ui"), applicationSettings_, nullptr, bs::UtxoReservationToken{}, this ); - } else { - dlg = std::make_shared(armory_, walletsMgr_, utxoReservationMgr_, signContainer_ - , logMgr_->logger("ui"), applicationSettings_, this); - } - - if (!selectedWalletId.empty()) { - dlg->SelectWallet(selectedWalletId, UiUtils::WalletsTypes::None); - } - - DisplayCreateTransactionDialog(dlg); -} - -void BSTerminalMainWindow::setupMenu() -{ - // menu role erquired for OSX only, to place it to first menu item - action_login_->setMenuRole(QAction::ApplicationSpecificRole); - action_logout_->setMenuRole(QAction::ApplicationSpecificRole); - - - ui_->menuFile->insertAction(ui_->actionSettings, action_login_); - ui_->menuFile->insertAction(ui_->actionSettings, action_logout_); - - ui_->menuFile->insertSeparator(action_login_); - ui_->menuFile->insertSeparator(ui_->actionSettings); - - AboutDialog *aboutDlg = new AboutDialog(applicationSettings_->get(ApplicationSettings::ChangeLog_Base_Url), this); - auto aboutDlgCb = [aboutDlg] (int tab) { - return [aboutDlg, tab]() { - aboutDlg->setTab(tab); - aboutDlg->show(); - }; - }; - - SupportDialog *supportDlg = new SupportDialog(this); - auto supportDlgCb = [supportDlg] (int tab, QString title) { - return [supportDlg, tab, title]() { - supportDlg->setTab(tab); - supportDlg->setWindowTitle(title); - supportDlg->show(); - }; - }; - - connect(ui_->actionCreateNewWallet, &QAction::triggered, this, [ww = ui_->widgetWallets]{ ww->onNewWallet(); }); - connect(ui_->actionOpenURI, &QAction::triggered, this, [this]{ openURIDialog(); }); - connect(ui_->actionAuthenticationAddresses, &QAction::triggered, this, &BSTerminalMainWindow::openAuthManagerDialog); - connect(ui_->actionSettings, &QAction::triggered, this, [=]() { openConfigDialog(); }); - connect(ui_->actionAccountInformation, &QAction::triggered, this, &BSTerminalMainWindow::openAccountInfoDialog); - connect(ui_->actionEnterColorCoinToken, &QAction::triggered, this, &BSTerminalMainWindow::openCCTokenDialog); - connect(ui_->actionAbout, &QAction::triggered, aboutDlgCb(0)); - connect(ui_->actionVersion, &QAction::triggered, aboutDlgCb(3)); - connect(ui_->actionGuides, &QAction::triggered, supportDlgCb(0, QObject::tr("Guides"))); - connect(ui_->actionVideoTutorials, &QAction::triggered, supportDlgCb(1, QObject::tr("Video Tutorials"))); - connect(ui_->actionContact, &QAction::triggered, supportDlgCb(2, QObject::tr("Support"))); - - onUserLoggedOut(); - -#ifndef Q_OS_MAC - ui_->horizontalFrame->hide(); - ui_->menubar->setCornerWidget(ui_->loginGroupWidget); -#endif - -#ifndef PRODUCTION_BUILD - auto envType = static_cast(applicationSettings_->get(ApplicationSettings::envConfiguration).toInt()); - bool isProd = envType == ApplicationSettings::EnvConfiguration::Production; - ui_->prodEnvSettings->setEnabled(!isProd); - ui_->testEnvSettings->setEnabled(isProd); - connect(ui_->prodEnvSettings, &QPushButton::clicked, this, [this] { - promptSwitchEnv(true); - }); - connect(ui_->testEnvSettings, &QPushButton::clicked, this, [this] { - promptSwitchEnv(false); - }); -#else - ui_->prodEnvSettings->setVisible(false); - ui_->testEnvSettings->setVisible(false); -#endif // !PRODUCTION_BUILD -} - -void BSTerminalMainWindow::openAuthManagerDialog() -{ - authAddrDlg_->exec(); -} - -void BSTerminalMainWindow::openConfigDialog(bool showInNetworkPage) -{ - auto oldEnv = static_cast( - applicationSettings_->get(ApplicationSettings::envConfiguration)); - - ConfigDialog configDialog(applicationSettings_, armoryServersProvider_, signersProvider_, signContainer_, walletsMgr_, this); - connect(&configDialog, &ConfigDialog::reconnectArmory, this, &BSTerminalMainWindow::onArmoryNeedsReconnect); - - if (showInNetworkPage) { - configDialog.popupNetworkSettings(); - } - - int rc = configDialog.exec(); - - UpdateMainWindowAppearence(); - - auto newEnv = static_cast( - applicationSettings_->get(ApplicationSettings::envConfiguration)); - if (rc == QDialog::Accepted && newEnv != oldEnv) { - bool prod = newEnv == ApplicationSettings::EnvConfiguration::Production; - BSMessageBox mbox(BSMessageBox::question - , tr("Environment selection") - , tr("Switch Environment") - , tr("Do you wish to change to the %1 environment now?").arg(prod ? tr("Production") : tr("Test")) - , this); - mbox.setConfirmButtonText(tr("Yes")); - int rc = mbox.exec(); - if (rc == QDialog::Accepted) { - restartTerminal(); - } - } -} - -void BSTerminalMainWindow::openAccountInfoDialog() -{ - CelerAccountInfoDialog dialog(celerConnection_, this); - dialog.exec(); -} - -void BSTerminalMainWindow::openCCTokenDialog() -{ - const auto lbdCCTokenDlg = [this] { - QMetaObject::invokeMethod(this, [this] { - CCTokenEntryDialog(walletsMgr_, ccFileManager_, applicationSettings_, this).exec(); - }); - }; - // Do not use deferredDialogs_ here as it will deadblock PuB public key processing - if (walletsMgr_->hasPrimaryWallet()) { - lbdCCTokenDlg(); - } -} - -void BSTerminalMainWindow::onLogin() -{ - if (!action_login_->isEnabled()) { - return; - } - auto envType = static_cast(applicationSettings_->get(ApplicationSettings::envConfiguration).toInt()); - - if (walletsSynched_ && !walletsMgr_->getPrimaryWallet()) { - addDeferredDialog([this] { - createPrimaryWallet(); - }); - return; - } - - auto bsClient = createClient(); - - auto logger = logMgr_->logger("proxy"); - LoginWindow loginDialog(logger, bsClient, applicationSettings_, this); - - int rc = loginDialog.exec(); - if (rc != QDialog::Accepted && !loginDialog.result()) { - return; - } - - bool isRegistered = (loginDialog.result()->userType == bs::network::UserType::Market - || loginDialog.result()->userType == bs::network::UserType::Trading - || loginDialog.result()->userType == bs::network::UserType::Dealing); - - if (!isRegistered && envType == ApplicationSettings::EnvConfiguration::Test) { - auto createTestAccountUrl = applicationSettings_->get(ApplicationSettings::GetAccount_UrlTest); - BSMessageBox dlg(BSMessageBox::info, tr("Create Test Account") - , tr("Create a BlockSettle test account") - , tr("

Login requires a test account - create one in minutes on test.blocksettle.com

" - "

Once you have registered, return to login in the Terminal.

" - "Create Test Account Now") - .arg(createTestAccountUrl).arg(BSMessageBox::kUrlColor), this); - dlg.setOkVisible(false); - dlg.setCancelVisible(true); - dlg.enableRichText(); - dlg.exec(); - return; - } - - if (!isRegistered && envType == ApplicationSettings::EnvConfiguration::Production) { - auto createAccountUrl = applicationSettings_->get(ApplicationSettings::GetAccount_UrlProd); - BSMessageBox dlg(BSMessageBox::info, tr("Create Account") - , tr("Create a BlockSettle account") - , tr("

Login requires an account - create one in minutes on blocksettle.com

" - "

Once you have registered, return to login in the Terminal.

" - "Create Account Now") - .arg(createAccountUrl).arg(BSMessageBox::kUrlColor), this); - dlg.setOkVisible(false); - dlg.setCancelVisible(true); - dlg.enableRichText(); - dlg.exec(); - return; - } - - activateClient(bsClient, *loginDialog.result(), loginDialog.email().toStdString()); -} - -void BSTerminalMainWindow::onLogout() -{ - ui_->widgetWallets->setUsername(QString()); - if (chatClientServicePtr_) { - chatClientServicePtr_->LogoutFromServer(); - } - ui_->widgetChart->disconnect(); - - if (celerConnection_->IsConnected()) { - celerConnection_->CloseConnection(); - } - - mdProvider_->UnsubscribeFromMD(); - - setLoginButtonText(loginButtonText_); - - setWidgetsAuthorized(false); - - bsClient_.reset(); -} - -void BSTerminalMainWindow::onUserLoggedIn() -{ - ui_->actionAccountInformation->setEnabled(true); - ui_->actionAuthenticationAddresses->setEnabled(celerConnection_->celerUserType() - != BaseCelerClient::CelerUserType::Market); - ui_->actionOneTimePassword->setEnabled(true); - ui_->actionEnterColorCoinToken->setEnabled(true); - - ui_->actionDeposits->setEnabled(true); - ui_->actionWithdrawalRequest->setEnabled(true); - ui_->actionLinkAdditionalBankAccount->setEnabled(true); - - ccFileManager_->ConnectToCelerClient(celerConnection_); - ui_->widgetRFQ->onUserConnected(userType_); - ui_->widgetRFQReply->onUserConnected(userType_); - - const auto userId = BinaryData::CreateFromHex(celerConnection_->userId()); - const auto &deferredDialog = [this, userId] { - walletsMgr_->setUserId(userId); - enableTradingIfNeeded(); - }; - addDeferredDialog(deferredDialog); - - setLoginButtonText(currentUserLogin_); -} - -void BSTerminalMainWindow::onUserLoggedOut() -{ - ui_->actionAccountInformation->setEnabled(false); - ui_->actionAuthenticationAddresses->setEnabled(false); - ui_->actionEnterColorCoinToken->setEnabled(false); - ui_->actionOneTimePassword->setEnabled(false); - - ui_->actionDeposits->setEnabled(false); - ui_->actionWithdrawalRequest->setEnabled(false); - ui_->actionLinkAdditionalBankAccount->setEnabled(false); - - if (walletsMgr_) { - walletsMgr_->setUserId(BinaryData{}); - } - if (authManager_) { - authManager_->OnDisconnectedFromCeler(); - } - - setLoginButtonText(loginButtonText_); -} - -void BSTerminalMainWindow::onAccountTypeChanged(bs::network::UserType userType, bool enabled) -{ - userType_ = userType; - - if (enabled != accountEnabled_ && userType != bs::network::UserType::Chat) { - accountEnabled_ = enabled; - NotificationCenter::notify(enabled ? bs::ui::NotifyType::AccountEnabled : bs::ui::NotifyType::AccountDisabled, {}); - } - - authManager_->setUserType(userType); - - ui_->widgetChat->setUserType(enabled ? userType : bs::network::UserType::Chat); -} - -void BSTerminalMainWindow::onCelerConnected() -{ - onUserLoggedIn(); - updateControlEnabledState(); -} - -void BSTerminalMainWindow::onCelerDisconnected() -{ - onUserLoggedOut(); - celerConnection_->CloseConnection(); - updateControlEnabledState(); -} - -void BSTerminalMainWindow::onCelerConnectionError(int errorCode) -{ - switch(errorCode) - { - case BaseCelerClient::LoginError: - logMgr_->logger("ui")->debug("[BSTerminalMainWindow::onCelerConnectionError] login failed. Probably user do not have BS matching account"); - break; - } -} - -struct BSTerminalMainWindow::TxInfo { - Tx tx; - uint32_t txTime{}; - int64_t value{}; - std::shared_ptr wallet; - bs::sync::Transaction::Direction direction{}; - QString mainAddress; -}; - -void BSTerminalMainWindow::onZCreceived(const std::vector &entries) -{ - if (entries.empty()) { - return; - } - for (const auto &entry : walletsMgr_->mergeEntries(entries)) { - const auto &cbTx = [this, entry] (const Tx &tx) - { - std::shared_ptr wallet; - for (const auto &walletId : entry.walletIds) { - wallet = walletsMgr_->getWalletById(walletId); - if (wallet) { - break; - } - } - if (!wallet) { - return; - } - - auto txInfo = std::make_shared(); - txInfo->tx = tx; - txInfo->txTime = entry.txTime; - txInfo->value = entry.value; - txInfo->wallet = wallet; - - const auto &cbDir = [this, txInfo] (bs::sync::Transaction::Direction dir, const std::vector &) { - txInfo->direction = dir; - if (!txInfo->mainAddress.isEmpty()) { - showZcNotification(*txInfo); - } - }; - - const auto &cbMainAddr = [this, txInfo] (const QString &mainAddr, int addrCount) { - txInfo->mainAddress = mainAddr; - if ((txInfo->direction != bs::sync::Transaction::Direction::Unknown)) { - showZcNotification(*txInfo); - } - }; - - walletsMgr_->getTransactionDirection(tx, wallet->walletId(), cbDir); - walletsMgr_->getTransactionMainAddress(tx, wallet->walletId(), (entry.value > 0), cbMainAddr); - }; - armory_->getTxByHash(entry.txHash, cbTx, true); - } -} - -void BSTerminalMainWindow::showZcNotification(const TxInfo &txInfo) -{ - QStringList lines; - lines << tr("Date: %1").arg(UiUtils::displayDateTime(txInfo.txTime)); - lines << tr("TX: %1 %2 %3").arg(tr(bs::sync::Transaction::toString(txInfo.direction))) - .arg(txInfo.wallet->displayTxValue(txInfo.value)).arg(txInfo.wallet->displaySymbol()); - lines << tr("Wallet: %1").arg(QString::fromStdString(txInfo.wallet->name())); - lines << (txInfo.tx.isRBF() ? tr("RBF Enabled") : tr("RBF Disabled")); - lines << txInfo.mainAddress; - - const auto &title = tr("New blockchain transaction"); - NotificationCenter::notify(bs::ui::NotifyType::BlockchainTX, { title, lines.join(tr("\n")) }); -} - -void BSTerminalMainWindow::onNodeStatus(NodeStatus nodeStatus, bool isSegWitEnabled, RpcStatus rpcStatus) -{ - // Do not use rpcStatus for node status check, it works unreliable for some reasons - bool isBitcoinCoreOnline = (nodeStatus == NodeStatus_Online); - if (isBitcoinCoreOnline != isBitcoinCoreOnline_) { - isBitcoinCoreOnline_ = isBitcoinCoreOnline; - if (isBitcoinCoreOnline) { - SPDLOG_LOGGER_INFO(logMgr_->logger(), "BlockSettleDB connected to Bitcoin Core RPC"); - NotificationCenter::notify(bs::ui::NotifyType::BitcoinCoreOnline, {}); - } else { - SPDLOG_LOGGER_ERROR(logMgr_->logger(), "BlockSettleDB disconnected from Bitcoin Core RPC"); - NotificationCenter::notify(bs::ui::NotifyType::BitcoinCoreOffline, {}); - } - } -} - -void BSTerminalMainWindow::showRunInBackgroundMessage() -{ - sysTrayIcon_->showMessage(tr("BlockSettle is running"), tr("BlockSettle Terminal is running in the backgroud. Click the tray icon to open the main window."), QIcon(QLatin1String(":/resources/login-logo.png"))); -} - -void BSTerminalMainWindow::closeEvent(QCloseEvent* event) -{ - if (applicationSettings_->get(ApplicationSettings::closeToTray)) { - hide(); - event->ignore(); - } - else { - if (chatClientServicePtr_) { - chatClientServicePtr_->LogoutFromServer(); - } - - QMainWindow::closeEvent(event); - QApplication::exit(); - } -} - -void BSTerminalMainWindow::changeEvent(QEvent* e) -{ - switch (e->type()) - { - case QEvent::WindowStateChange: - { - if (this->windowState() & Qt::WindowMinimized) - { - if (applicationSettings_->get(ApplicationSettings::minimizeToTray)) - { - QTimer::singleShot(0, this, &QMainWindow::hide); - } - } - - break; - } - default: - break; - } - - QMainWindow::changeEvent(e); -} - -void BSTerminalMainWindow::setLoginButtonText(const QString& text) -{ - auto *button = ui_->pushButtonUser; - button->setText(text); - button->setProperty("usernameButton", QVariant(text == loginButtonText_)); - button->setProperty("usernameButtonLoggedIn", QVariant(text != loginButtonText_)); - button->style()->unpolish(button); - button->style()->polish(button); - button->update(); - -#ifndef Q_OS_MAC - ui_->menubar->adjustSize(); -#endif -} - -void BSTerminalMainWindow::onCCLoaded() -{ - walletsMgr_->onCCInfoLoaded(); - - const auto ccResolver = walletsMgr_->ccResolver(); - if (ccResolver && signContainer_) { - deferCCsync_ = false; - signContainer_->syncCCNames(ccResolver->securities()); - } - else { - deferCCsync_ = true; - } -} - -void BSTerminalMainWindow::setupShortcuts() -{ - auto overviewTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+1")), this); - overviewTabShortcut->setContext(Qt::WindowShortcut); - connect(overviewTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(0);}); - - auto tradingTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+2")), this); - tradingTabShortcut->setContext(Qt::WindowShortcut); - connect(tradingTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(1);}); - - auto dealingTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+3")), this); - dealingTabShortcut->setContext(Qt::WindowShortcut); - connect(dealingTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(2);}); - - auto walletsTabShortcutt = new QShortcut(QKeySequence(QStringLiteral("Ctrl+4")), this); - walletsTabShortcutt->setContext(Qt::WindowShortcut); - connect(walletsTabShortcutt, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(3);}); - - auto transactionsTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+5")), this); - transactionsTabShortcut->setContext(Qt::WindowShortcut); - connect(transactionsTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(4);}); - - auto explorerTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+6")), this); - explorerTabShortcut->setContext(Qt::WindowShortcut); - connect(explorerTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(5);}); - - auto chartsTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+7")), this); - chartsTabShortcut->setContext(Qt::WindowShortcut); - connect(chartsTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(6);}); - - auto chatTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+8")), this); - chatTabShortcut->setContext(Qt::WindowShortcut); - connect(chatTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(7);}); - - // TODO: Switch ChatWidget to TabWithShortcut if needed (it will ignore shortcuts right now) - - auto addShotcut = [this](const char *keySequence, TabWithShortcut::ShortcutType type) { - auto shortcut = new QShortcut(QKeySequence(QLatin1String(keySequence)), this); - shortcut->setContext(Qt::WindowShortcut); - connect(shortcut, &QShortcut::activated, [this, type]() { - auto widget = dynamic_cast(ui_->tabWidget->currentWidget()); - if (widget) { - widget->shortcutActivated(type); - } - }); - }; - - addShotcut("Alt+1", TabWithShortcut::ShortcutType::Alt_1); - addShotcut("Alt+2", TabWithShortcut::ShortcutType::Alt_2); - addShotcut("Alt+3", TabWithShortcut::ShortcutType::Alt_3); - addShotcut("Ctrl+S", TabWithShortcut::ShortcutType::Ctrl_S); - addShotcut("Ctrl+P", TabWithShortcut::ShortcutType::Ctrl_P); - addShotcut("Ctrl+Q", TabWithShortcut::ShortcutType::Ctrl_Q); - addShotcut("Alt+S", TabWithShortcut::ShortcutType::Alt_S); - addShotcut("Alt+B", TabWithShortcut::ShortcutType::Alt_B); - addShotcut("Alt+P", TabWithShortcut::ShortcutType::Alt_P); -} - -void BSTerminalMainWindow::onButtonUserClicked() { - if (ui_->pushButtonUser->text() == loginButtonText_) { - onLogin(); - } else { - if (BSMessageBox(BSMessageBox::question, tr("User Logout"), tr("You are about to logout") - , tr("Do you want to continue?")).exec() == QDialog::Accepted) - onLogout(); - } -} - -void BSTerminalMainWindow::showArmoryServerPrompt(const BinaryData &srvPubKey, const std::string &srvIPPort, std::shared_ptr> promiseObj) -{ - QList servers = armoryServersProvider_->servers(); - int serverIndex = armoryServersProvider_->indexOfIpPort(srvIPPort); - if (serverIndex >= 0) { - ArmoryServer server = servers.at(serverIndex); - - const auto &deferredDialog = [this, server, promiseObj, srvPubKey, srvIPPort]{ - if (server.armoryDBKey.isEmpty()) { - ImportKeyBox box(BSMessageBox::question - , tr("Import BlockSettleDB ID Key?") - , this); - - box.setNewKeyFromBinary(srvPubKey); - box.setAddrPort(srvIPPort); - - bool answer = (box.exec() == QDialog::Accepted); - - if (answer) { - armoryServersProvider_->addKey(srvIPPort, srvPubKey); - } - - promiseObj->set_value(true); - } - else if (server.armoryDBKey.toStdString() != srvPubKey.toHexStr()) { - ImportKeyBox box(BSMessageBox::question - , tr("Import BlockSettleDB ID Key?") - , this); - - box.setNewKeyFromBinary(srvPubKey); - box.setOldKey(server.armoryDBKey); - box.setAddrPort(srvIPPort); - box.setCancelVisible(true); - - bool answer = (box.exec() == QDialog::Accepted); - - if (answer) { - armoryServersProvider_->addKey(srvIPPort, srvPubKey); - } - - promiseObj->set_value(answer); - } - else { - promiseObj->set_value(true); - } - }; - - addDeferredDialog(deferredDialog); - } - else { - // server not in the list - added directly to ini config - promiseObj->set_value(true); - } -} - -void BSTerminalMainWindow::onArmoryNeedsReconnect() -{ - disconnect(statusBarView_.get(), nullptr, nullptr, nullptr); - statusBarView_->deleteLater(); - QApplication::processEvents(); - - initArmory(); - LoadWallets(); - - QApplication::processEvents(); - - statusBarView_ = std::make_shared(armory_, walletsMgr_, assetManager_, celerConnection_ - , signContainer_, ui_->statusbar); - - InitWalletsView(); - - - InitSigningContainer(); - InitAuthManager(); - - connectSigner(); - connectArmory(); -} - -void BSTerminalMainWindow::onTabWidgetCurrentChanged(const int &index) -{ - const int chatIndex = ui_->tabWidget->indexOf(ui_->widgetChat); - const bool isChatTab = index == chatIndex; - //ui_->widgetChat->updateChat(isChatTab); -} - -void BSTerminalMainWindow::onSyncWallets() -{ - if (walletsMgr_->isSynchronising()) { - return; - } - - wasWalletsRegistered_ = false; - walletsSynched_ = false; - const auto &progressDelegate = [this](int cur, int total) { - logMgr_->logger()->debug("Loaded wallet {} of {}", cur, total); - }; - - walletsMgr_->reset(); - walletsMgr_->syncWallets(progressDelegate); - updateControlEnabledState(); -} - -void BSTerminalMainWindow::onSignerVisibleChanged() -{ - processDeferredDialogs(); -} - -void BSTerminalMainWindow::InitWidgets() -{ - authAddrDlg_ = std::make_shared(logMgr_->logger(), authManager_ - , assetManager_, applicationSettings_, this); - - InitWalletsView(); - InitPortfolioView(); - - ui_->widgetRFQ->initWidgets(mdProvider_, mdCallbacks_, applicationSettings_); - - auto quoteProvider = std::make_shared(assetManager_ - , logMgr_->logger("message")); - quoteProvider->ConnectToCelerClient(celerConnection_); - - const auto &logger = logMgr_->logger(); - const auto aqScriptRunner = new AQScriptRunner(quoteProvider, signContainer_ - , mdCallbacks_, assetManager_, logger); - if (!applicationSettings_->get(ApplicationSettings::ExtConnName).empty() - && !applicationSettings_->get(ApplicationSettings::ExtConnHost).empty() - && !applicationSettings_->get(ApplicationSettings::ExtConnPort).empty() - && !applicationSettings_->get(ApplicationSettings::ExtConnPubKey).empty()) { - ExtConnections extConns; - logger->debug("Setting up ext connection"); - - const auto &clientKeyPath = SystemFilePaths::appDataLocation() + "/extConnKey"; - bs::network::ws::PrivateKey privKeyClient; - std::ifstream privKeyReader(clientKeyPath, std::ios::binary); - if (privKeyReader.is_open()) { - std::string str; - str.assign(std::istreambuf_iterator(privKeyReader) - , std::istreambuf_iterator()); - privKeyClient.reserve(str.size()); - std::for_each(str.cbegin(), str.cend(), [&privKeyClient](char c) { - privKeyClient.push_back(c); - }); - } - if (privKeyClient.empty()) { - logger->debug("Creating new ext connection key"); - privKeyClient = bs::network::ws::generatePrivKey(); - std::ofstream privKeyWriter(clientKeyPath, std::ios::out|std::ios::binary); - privKeyWriter.write((char *)&privKeyClient[0], privKeyClient.size()); - const auto &pubKeyClient = bs::network::ws::publicKey(privKeyClient); - applicationSettings_->set(ApplicationSettings::ExtConnOwnPubKey - , QString::fromStdString(bs::toHex(pubKeyClient))); - } - const auto &certClient = bs::network::ws::generateSelfSignedCert(privKeyClient); - const auto &srvPubKey = applicationSettings_->get(ApplicationSettings::ExtConnPubKey); - SslDataConnectionParams clientParams; - clientParams.useSsl = true; - clientParams.cert = certClient; - clientParams.privKey = privKeyClient; - clientParams.allowSelfSigned = true; - clientParams.skipHostNameChecks = true; - clientParams.verifyCallback = [srvPubKey, this](const std::string &pubKey) -> bool { - if (BinaryData::CreateFromHex(srvPubKey).toBinStr() == pubKey) { - return true; - } - QMetaObject::invokeMethod(this, [pubKey] { - BSMessageBox(BSMessageBox::warning, tr("External Connection error") - , tr("Invalid server key: %1").arg(QString::fromStdString(bs::toHex(pubKey)))).exec(); - }); - return false; - }; - - RetryingDataConnectionParams retryingParams; - retryingParams.connection = std::make_unique(logger, clientParams); - auto connection = std::make_shared(logger, std::move(retryingParams)); - - if (connection->openConnection(applicationSettings_->get(ApplicationSettings::ExtConnHost) - , applicationSettings_->get(ApplicationSettings::ExtConnPort) - , aqScriptRunner->getExtConnListener().get())) { - extConns[applicationSettings_->get(ApplicationSettings::ExtConnName)] = connection; - } - aqScriptRunner->setExtConnections(extConns); - } - - autoSignQuoteProvider_ = std::make_shared(logger - , aqScriptRunner, applicationSettings_, signContainer_, celerConnection_); - - const auto rfqScriptRunner = new RFQScriptRunner(mdCallbacks_, logger, nullptr); - autoSignRFQProvider_ = std::make_shared(logger - , rfqScriptRunner, applicationSettings_, signContainer_, celerConnection_); - - auto dialogManager = std::make_shared(this); - - ui_->widgetRFQ->init(logger, celerConnection_, authManager_, quoteProvider - , assetManager_, dialogManager, signContainer_, armory_, autoSignRFQProvider_ - , utxoReservationMgr_, orderListModel_.get()); - ui_->widgetRFQReply->init(logger, celerConnection_, authManager_ - , quoteProvider, mdCallbacks_, assetManager_, applicationSettings_, dialogManager - , signContainer_, armory_, connectionManager_, autoSignQuoteProvider_ - , utxoReservationMgr_, orderListModel_.get()); - - connect(ui_->widgetRFQ, &RFQRequestWidget::requestPrimaryWalletCreation, this - , &BSTerminalMainWindow::onCreatePrimaryWalletRequest); - connect(ui_->widgetRFQReply, &RFQReplyWidget::requestPrimaryWalletCreation, this - , &BSTerminalMainWindow::onCreatePrimaryWalletRequest); - connect(ui_->widgetRFQ, &RFQRequestWidget::loginRequested, this - , &BSTerminalMainWindow::onLogin); - - connect(ui_->tabWidget, &QTabWidget::tabBarClicked, this, - [requestRFQ = QPointer(ui_->widgetRFQ) - , replyRFQ = QPointer(ui_->widgetRFQReply) - , tabWidget = QPointer(ui_->tabWidget)] (int index) - { - if (!tabWidget) { - return; - } - if (requestRFQ && requestRFQ == tabWidget->widget(index)) { - requestRFQ->forceCheckCondition(); - } - if (replyRFQ && replyRFQ == tabWidget->widget(index)) { - replyRFQ->forceCheckCondition(); - } - }); -} - -void BSTerminalMainWindow::enableTradingIfNeeded() -{ - // Can't proceed without userId - if (!walletsMgr_->isUserIdSet()) { - return; - } - - auto enableTradingFunc = [this](const std::shared_ptr &wallet) { - addDeferredDialog([this, wallet] { - BSMessageBox qry(BSMessageBox::question, tr("Upgrade Wallet"), tr("Enable Trading") - , tr("BlockSettle requires you to hold sub-wallets with Authentication Addresses to interact with our trading system.

" - "You will be able to trade up to %1 bitcoin per trade once your Authentication Address has been submitted.

" - "After %2 trades your Authentication Address will be verified and your trading limit removed.

" - "Do you wish to enable XBT trading?").arg(bs::XBTAmount(tradeSettings_->xbtTier1Limit).GetValueBitcoin()).arg(tradeSettings_->authRequiredSettledTrades) - , this); - qry.enableRichText(); - if (qry.exec() == QDialog::Accepted) { - walletsMgr_->EnableXBTTradingInWallet(wallet->walletId(), [this](bs::error::ErrorCode result) { - if (result == bs::error::ErrorCode::NoError) { - // If wallet was promoted to primary we could try to get chat keys now - tryGetChatKeys(); - walletsMgr_->setUserId(BinaryData::CreateFromHex(celerConnection_->userId())); - } - }); - } - }); - }; - - auto primaryWallet = walletsMgr_->getPrimaryWallet(); - if (primaryWallet) { - if (!primaryWallet->tradingEnabled()) { - enableTradingFunc(primaryWallet); - } - } -} - -void BSTerminalMainWindow::showLegacyWarningIfNeeded() -{ - if (applicationSettings_->get(ApplicationSettings::HideLegacyWalletWarning)) { - return; - } - applicationSettings_->set(ApplicationSettings::HideLegacyWalletWarning, true); - addDeferredDialog([this] { - int forcedWidth = 640; - BSMessageBox mbox(BSMessageBox::info - , tr("Legacy Wallets") - , tr("Legacy Address Balances") - , tr("The BlockSettle Terminal has detected the use of legacy addresses in your wallet.\n\n" - "The BlockSettle Terminal supports viewing and spending from legacy addresses, but will not support the following actions related to these addresses:\n\n" - "- GUI support for legacy address generation\n" - "- Trading and settlement using legacy inputs\n\n" - "BlockSettle strongly recommends that you move your legacy address balances to native SegWit addresses.") - , {} - , forcedWidth - , this); - mbox.exec(); - }); -} - -void BSTerminalMainWindow::promptSwitchEnv(bool prod) -{ - BSMessageBox mbox(BSMessageBox::question - , tr("Environment selection") - , tr("Switch Environment") - , tr("Do you wish to change to the %1 environment now?").arg(prod ? tr("Production") : tr("Test")) - , this); - mbox.setConfirmButtonText(tr("Yes")); - int rc = mbox.exec(); - if (rc == QDialog::Accepted) { - if (prod) { - switchToProdEnv(); - } else { - switchToTestEnv(); - } - restartTerminal(); - } -} - -void BSTerminalMainWindow::switchToTestEnv() -{ - applicationSettings_->set(ApplicationSettings::envConfiguration - , static_cast(ApplicationSettings::EnvConfiguration::Test)); - armoryServersProvider_->setupServer(armoryServersProvider_->getIndexOfTestNetServer()); -} - -void BSTerminalMainWindow::switchToProdEnv() -{ - applicationSettings_->set(ApplicationSettings::envConfiguration - , static_cast(ApplicationSettings::EnvConfiguration::Production)); - armoryServersProvider_->setupServer(armoryServersProvider_->getIndexOfMainNetServer()); -} - -void BSTerminalMainWindow::restartTerminal() -{ - lockFile_.unlock(); - QProcess::startDetached(qApp->arguments()[0], qApp->arguments()); - qApp->quit(); -} - -void BSTerminalMainWindow::processDeferredDialogs() -{ - if(deferredDialogRunning_) { - return; - } - if (signContainer_ && signContainer_->isLocal() && signContainer_->isWindowVisible()) { - return; - } - - deferredDialogRunning_ = true; - while (!deferredDialogs_.empty()) { - deferredDialogs_.front()(); // run stored lambda - deferredDialogs_.pop(); - } - deferredDialogRunning_ = false; -} - -std::shared_ptr BSTerminalMainWindow::createClient() -{ - auto logger = logMgr_->logger("proxy"); - auto bsClient = std::make_shared(logger); - - bs::network::BIP15xParams params; - params.ephemeralPeers = true; - params.authMode = bs::network::BIP15xAuthMode::OneWay; - const auto &bip15xTransport = std::make_shared(logger, params); - bip15xTransport->setKeyCb(cbApproveProxy_); - - auto wsConnection = std::make_unique(logger, WsDataConnectionParams{}); - auto connection = std::make_unique(logger, std::move(wsConnection), bip15xTransport); - auto env = static_cast( - applicationSettings_->get(ApplicationSettings::envConfiguration)); - bool result = connection->openConnection(PubKeyLoader::serverHostName(PubKeyLoader::KeyType::Proxy, env) - , PubKeyLoader::serverHttpPort(), bsClient.get()); - assert(result); - bsClient->setConnection(std::move(connection)); - - // Must be connected before loginDialog.exec call (balances could be received before loginDialog.exec returns)! - connect(bsClient.get(), &BsClient::balanceLoaded, assetManager_.get(), &AssetManager::fxBalanceLoaded); - connect(bsClient.get(), &BsClient::balanceUpdated, assetManager_.get(), &AssetManager::onAccountBalanceLoaded); - - return bsClient; -} - -void BSTerminalMainWindow::activateClient(const std::shared_ptr &bsClient - , const BsClientLoginResult &result, const std::string &email) -{ - currentUserLogin_ = QString::fromStdString(email); - - chatTokenData_ = result.chatTokenData; - chatTokenSign_ = result.chatTokenSign; - tryLoginIntoChat(); - - bsClient_ = bsClient; - ccFileManager_->setBsClient(bsClient); - authAddrDlg_->setBsClient(bsClient); - - tradeSettings_ = std::make_shared(result.tradeSettings); - applicationSettings_->set(ApplicationSettings::SubmittedAddressXbtLimit, static_cast(tradeSettings_->xbtTier1Limit)); - - authManager_->initLogin(celerConnection_, tradeSettings_); - - onBootstrapDataLoaded(result.bootstrapDataSigned); - - connect(bsClient_.get(), &BsClient::disconnected, orderListModel_.get(), &OrderListModel::onDisconnected); - connect(bsClient_.get(), &BsClient::disconnected, this, &BSTerminalMainWindow::onBsConnectionDisconnected); - connect(bsClient_.get(), &BsClient::connectionFailed, this, &BSTerminalMainWindow::onBsConnectionFailed); - - // connect to RFQ dialog - connect(bsClient_.get(), &BsClient::processPbMessage, ui_->widgetRFQ, &RFQRequestWidget::onMessageFromPB); - connect(bsClient_.get(), &BsClient::disconnected, ui_->widgetRFQ, &RFQRequestWidget::onUserDisconnected); - connect(ui_->widgetRFQ, &RFQRequestWidget::sendUnsignedPayinToPB, bsClient_.get(), &BsClient::sendUnsignedPayin); - connect(ui_->widgetRFQ, &RFQRequestWidget::sendSignedPayinToPB, bsClient_.get(), &BsClient::sendSignedPayin); - connect(ui_->widgetRFQ, &RFQRequestWidget::sendSignedPayoutToPB, bsClient_.get(), &BsClient::sendSignedPayout); - - connect(ui_->widgetRFQ, &RFQRequestWidget::cancelXBTTrade, bsClient_.get(), &BsClient::sendCancelOnXBTTrade); - connect(ui_->widgetRFQ, &RFQRequestWidget::cancelCCTrade, bsClient_.get(), &BsClient::sendCancelOnCCTrade); - - // connect to quote dialog - connect(bsClient_.get(), &BsClient::processPbMessage, ui_->widgetRFQReply, &RFQReplyWidget::onMessageFromPB); - connect(ui_->widgetRFQReply, &RFQReplyWidget::sendUnsignedPayinToPB, bsClient_.get(), &BsClient::sendUnsignedPayin); - connect(ui_->widgetRFQReply, &RFQReplyWidget::sendSignedPayinToPB, bsClient_.get(), &BsClient::sendSignedPayin); - connect(ui_->widgetRFQReply, &RFQReplyWidget::sendSignedPayoutToPB, bsClient_.get(), &BsClient::sendSignedPayout); - - connect(ui_->widgetRFQReply, &RFQReplyWidget::cancelXBTTrade, bsClient_.get(), &BsClient::sendCancelOnXBTTrade); - connect(ui_->widgetRFQReply, &RFQReplyWidget::cancelCCTrade, bsClient_.get(), &BsClient::sendCancelOnCCTrade); - - connect(ui_->widgetChat, &ChatWidget::emailHashRequested, bsClient_.get(), &BsClient::findEmailHash); - connect(bsClient_.get(), &BsClient::emailHashReceived, ui_->widgetChat, &ChatWidget::onEmailHashReceived); - - connect(bsClient_.get(), &BsClient::processPbMessage, orderListModel_.get(), &OrderListModel::onMessageFromPB); - - utxoReservationMgr_->setFeeRatePb(result.feeRatePb); - connect(bsClient_.get(), &BsClient::feeRateReceived, this, [this] (float feeRate) { - utxoReservationMgr_->setFeeRatePb(feeRate); - }); - - setLoginButtonText(currentUserLogin_); - setWidgetsAuthorized(true); - - // We don't use password here, BsProxy will manage authentication - SPDLOG_LOGGER_DEBUG(logMgr_->logger(), "got celer login: {}", result.celerLogin); - celerConnection_->LoginToServer(bsClient_.get(), result.celerLogin, email); - - ui_->widgetWallets->setUsername(currentUserLogin_); - action_logout_->setVisible(false); - action_login_->setEnabled(false); - - // Market data, charts and chat should be available for all Auth eID logins - mdProvider_->SubscribeToMD(); - - connect(bsClient_.get(), &BsClient::processPbMessage, ui_->widgetChat, &ChatWidget::onProcessOtcPbMessage); - connect(ui_->widgetChat, &ChatWidget::sendOtcPbMessage, bsClient_.get(), &BsClient::sendPbMessage); - - connect(bsClient_.get(), &BsClient::bootstrapDataUpdated, this, [this](const std::string& data) { - onBootstrapDataLoaded(data); - }); - - accountEnabled_ = true; - onAccountTypeChanged(result.userType, result.enabled); - connect(bsClient_.get(), &BsClient::accountStateChanged, this, [this](bs::network::UserType userType, bool enabled) { - onAccountTypeChanged(userType, enabled); - }); - - connect(bsClient_.get(), &BsClient::tradingStatusChanged, this, [](bool tradingEnabled) { - NotificationCenter::notify(tradingEnabled ? bs::ui::NotifyType::TradingEnabledOnPB : bs::ui::NotifyType::TradingDisabledOnPB, {}); - }); -} - -const std::string &BSTerminalMainWindow::loginApiKeyEncrypted() const -{ - return loginApiKeyEncrypted_; -} - -void BSTerminalMainWindow::initApiKeyLogins() -{ - if (loginTimer_ || loginApiKeyEncrypted().empty() || !gotChatKeys_) { - return; - } - loginTimer_ = new QTimer(this); - connect(loginTimer_, &QTimer::timeout, this, [this] { - tryLoginUsingApiKey(); - }); - tryLoginUsingApiKey(); - loginTimer_->start(kAutoLoginTimer); -} - -void BSTerminalMainWindow::tryLoginUsingApiKey() -{ - if (loginApiKeyEncrypted().empty() || autoLoginState_ != AutoLoginState::Idle) { - return; - } - - auto logger = logMgr_->logger("proxy"); - autoLoginClient_ = createClient(); - - auto apiKeyErrorCb = [this, logger](AutoLoginState newState, const QString &errorMsg) { - // Do not show related errors multiple times - if (autoLoginState_ == AutoLoginState::Idle || autoLoginState_ == AutoLoginState::Failed) { - return; - } - SPDLOG_LOGGER_ERROR(logger, "authorization failed: {}", errorMsg.toStdString()); - autoLoginState_ = newState; - autoLoginClient_ = nullptr; - if (autoLoginLastErrorMsg_ != errorMsg) { - autoLoginLastErrorMsg_ = errorMsg; - BSMessageBox(BSMessageBox::critical, tr("API key login") - , tr("Login failed") - , errorMsg - , this).exec(); - } - }; - - connect(autoLoginClient_.get(), &BsClient::connected, this, [this, logger, apiKeyErrorCb] { - connect(autoLoginClient_.get(), &BsClient::authorizeDone, this, [this, logger, apiKeyErrorCb] - (BsClient::AuthorizeError error, const std::string &email) { - if (error != BsClient::AuthorizeError::NoError) { - switch (error) { - case BsClient::AuthorizeError::UnknownIpAddr: - apiKeyErrorCb(AutoLoginState::Failed, tr("Unexpected IP address")); - break; - case BsClient::AuthorizeError::UnknownApiKey: - apiKeyErrorCb(AutoLoginState::Failed, tr("API key not found")); - break; - case BsClient::AuthorizeError::Timeout: - apiKeyErrorCb(AutoLoginState::Idle, tr("Request timeout")); - break; - default: - apiKeyErrorCb(AutoLoginState::Idle, tr("Unknown server error")); - break; - } - return; - } - - connect(autoLoginClient_.get(), &BsClient::getLoginResultDone, this, [this, logger, email, apiKeyErrorCb] - (const BsClientLoginResult &result) { - if (result.status != AutheIDClient::NoError) { - apiKeyErrorCb(AutoLoginState::Idle, tr("Login failed")); - return; - } - activateClient(autoLoginClient_, result, email); - autoLoginState_ = AutoLoginState::Connected; - autoLoginClient_ = nullptr; - autoLoginLastErrorMsg_.clear(); - }); - autoLoginClient_->getLoginResult(); - }); - - SecureBinaryData apiKeyEncCopy; - try { - apiKeyEncCopy = SecureBinaryData::CreateFromHex(loginApiKeyEncrypted()); - } catch (...) { - apiKeyErrorCb(AutoLoginState::Failed, tr("Encrypted API key invalid")); - return; - } - - ConfigDialog::decryptData(walletsMgr_, signContainer_, apiKeyEncCopy, [this, apiKeyErrorCb] - (ConfigDialog::EncryptError error, const SecureBinaryData &data) { - if (error != ConfigDialog::EncryptError::NoError) { - apiKeyErrorCb(AutoLoginState::Failed, ConfigDialog::encryptErrorStr(error)); - return; - } - autoLoginClient_->authorize(data.toBinStr()); - }); - }); - - connect(autoLoginClient_.get(), &BsClient::disconnected, this, [logger, apiKeyErrorCb] { - apiKeyErrorCb(AutoLoginState::Idle, tr("Proxy disconnected")); - }); - connect(autoLoginClient_.get(), &BsClient::connectionFailed, this, [logger, apiKeyErrorCb] { - apiKeyErrorCb(AutoLoginState::Idle, tr("Proxy connection failed")); - }); - - autoLoginState_ = AutoLoginState::Connecting; -} - -void BSTerminalMainWindow::addDeferredDialog(const std::function &deferredDialog) -{ - // multi thread scope, it's safe to call this function from different threads - QMetaObject::invokeMethod(this, [this, deferredDialog] { - // single thread scope (main thread), it's safe to push to deferredDialogs_ - // and check deferredDialogRunning_ variable - deferredDialogs_.push(deferredDialog); - processDeferredDialogs(); - }, Qt::QueuedConnection); -} - -void BSTerminalMainWindow::openURIDialog() -{ - const bool testnetNetwork = applicationSettings_->get(ApplicationSettings::netType) == NetworkType::TestNet; - - auto uiLogger = logMgr_->logger("ui"); - - OpenURIDialog dlg{connectionManager_->GetNAM(), testnetNetwork, uiLogger, this}; - if (dlg.exec() == QDialog::Accepted) { - // open create transaction dialog - - const auto requestInfo = dlg.getRequestInfo(); - std::shared_ptr cerateTxDlg; - - if (applicationSettings_->get(ApplicationSettings::AdvancedTxDialogByDefault)) { - cerateTxDlg = CreateTransactionDialogAdvanced::CreateForPaymentRequest(armory_, walletsMgr_ - , utxoReservationMgr_, signContainer_, uiLogger, applicationSettings_ - , requestInfo, this); - } else { - cerateTxDlg = CreateTransactionDialogSimple::CreateForPaymentRequest(armory_, walletsMgr_ - , utxoReservationMgr_, signContainer_, uiLogger, applicationSettings_ - , requestInfo, this); - } - - DisplayCreateTransactionDialog(cerateTxDlg); - } -} - -void BSTerminalMainWindow::DisplayCreateTransactionDialog(std::shared_ptr dlg) -{ - while(true) { - dlg->exec(); - - if ((dlg->result() != QDialog::Accepted) || !dlg->switchModeRequested()) { - break; - } - - auto nextDialog = dlg->SwitchMode(); - dlg = nextDialog; - } -} - -void BSTerminalMainWindow::onBootstrapDataLoaded(const std::string& data) -{ - if (bootstrapDataManager_->setReceivedData(data)) { - authManager_->SetLoadedValidationAddressList(bootstrapDataManager_->GetAuthValidationList()); - ccFileManager_->SetLoadedDefinitions(bootstrapDataManager_->GetCCDefinitions()); - } -} - -void BSTerminalMainWindow::onAuthLeafCreated() -{ - auto authWallet = walletsMgr_->getAuthWallet(); - if (authWallet != nullptr) { - // check that current wallet has auth address that was submitted at some point - // if there is no such address - display auth address dialog, so user could submit - auto submittedAddresses = celerConnection_->GetSubmittedAuthAddressSet(); - auto existingAddresses = authWallet->getUsedAddressList(); - - bool haveSubmittedAddress = false; - for ( const auto& address : existingAddresses) { - if (submittedAddresses.find(address.display()) != submittedAddresses.end()) { - haveSubmittedAddress = true; - break; - } - } - - if (!haveSubmittedAddress) { - addDeferredDialog([this]() - { - openAuthManagerDialog(); - }); - } - } -} diff --git a/BlockSettleUILib/BSTerminalMainWindow.h b/BlockSettleUILib/BSTerminalMainWindow.h deleted file mode 100644 index 4ebdf0020..000000000 --- a/BlockSettleUILib/BSTerminalMainWindow.h +++ /dev/null @@ -1,373 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __BS_TERMINAL_MAIN_WINDOW_H__ -#define __BS_TERMINAL_MAIN_WINDOW_H__ - -#include -#include - -#include -#include - -#include "ApplicationSettings.h" -#include "ArmoryObject.h" -#include "BsClient.h" -#include "CelerClientProxy.h" -#include "QWalletInfo.h" -#include "SignContainer.h" -#include "WalletSignerContainer.h" -#include "BIP15xHelpers.h" - -#include "ChatProtocol/ChatClientService.h" - -namespace Ui { - class BSTerminalMainWindow; -} -namespace bs { - class LogManager; - class UTXOReservationManager; - struct TradeSettings; - namespace sync { - class Wallet; - class WalletsManager; - } -} - -class QLockFile; - -struct BsClientLoginResult; -struct NetworkSettings; - -class AboutDialog; -class ArmoryServersProvider; -class AssetManager; -class AuthAddressDialog; -class AuthAddressManager; -class AutheIDClient; -class AutoSignScriptProvider; -class BSMarketDataProvider; -class BSTerminalSplashScreen; -class BaseCelerClient; -class BootstrapDataManager; -class CCFileManager; -class CCPortfolioModel; -class CcTrackerClient; -class ConnectionManager; -class CreateTransactionDialog; -class LoginWindow; -class MDCallbacksQt; -class OrderListModel; -class QSystemTrayIcon; -class RequestReplyCommand; -class SignersProvider; -class StatusBarView; -class StatusViewBlockListener; -class TransactionsViewModel; -class WalletManagementWizard; - -enum class BootstrapFileError: int; - -class BSTerminalMainWindow : public QMainWindow -{ -Q_OBJECT - -public: - BSTerminalMainWindow(const std::shared_ptr& settings - , BSTerminalSplashScreen& splashScreen, QLockFile &lockFile, QWidget* parent = nullptr); - ~BSTerminalMainWindow() override; - - void postSplashscreenActions(); - void loadPositionAndShow(); - - bool event(QEvent *event) override; - void addDeferredDialog(const std::function &deferredDialog); - -private: - void setupToolbar(); - void setupTopRightWidget(); - void setupMenu(); - void setupIcon(); - - void setupWalletsView(); - void setupTransactionsView(); - void setupInfoWidget(); - - void initConnections(); - void initArmory(); - void initCcClient(); - void initUtxoReservationManager(); - void initBootstrapDataManager(); - void connectArmory(); - void connectCcClient(); - void connectSigner(); - std::shared_ptr createSigner(); - std::shared_ptr createRemoteSigner(bool restoreHeadless = false); - std::shared_ptr createLocalSigner(); - - void setTabStyle(); - - void LoadWallets(); - void InitAuthManager(); - bool InitSigningContainer(); - void InitAssets(); - - void InitPortfolioView(); - void InitWalletsView(); - void InitChartsView(); - - void tryInitChatView(); - void tryLoginIntoChat(); - void resetChatKeys(); - void tryGetChatKeys(); - - void UpdateMainWindowAppearence(); - - bool isMDLicenseAccepted() const; - void saveUserAcceptedMDLicense(); - - bool showStartupDialog(); - void setWidgetsAuthorized(bool authorized); - - void openURIDialog(); - -signals: - void armoryServerPromptResultReady(); - -private slots: - void InitTransactionsView(); - void ArmoryIsOffline(); - void SignerReady(); - void onNeedNewWallet(); - void showInfo(const QString &title, const QString &text); - void showError(const QString &title, const QString &text); - void onSignerConnError(SignContainer::ConnectionError error, const QString &details); - - void CompleteUIOnlineView(); - void CompleteDBConnection(); - - bool createPrimaryWallet(); - void onCreatePrimaryWalletRequest(); - - void acceptMDAgreement(); - void updateControlEnabledState(); - void onButtonUserClicked(); - void showArmoryServerPrompt(const BinaryData& srvPubKey, const std::string& srvIPPort, std::shared_ptr > promiseObj); - - void onArmoryNeedsReconnect(); - void onCCLoaded(); - - void onTabWidgetCurrentChanged(const int &index); - void onSyncWallets(); - void onSignerVisibleChanged(); - - void onAuthLeafCreated(); - -private: - std::unique_ptr ui_; - QAction *action_send_ = nullptr; - QAction *action_generate_address_ = nullptr; - QAction *action_login_ = nullptr; - QAction *action_logout_ = nullptr; - - std::shared_ptr logMgr_; - std::shared_ptr applicationSettings_; - std::shared_ptr walletsMgr_; - std::shared_ptr armoryServersProvider_; - std::shared_ptr signersProvider_; - std::shared_ptr authManager_; - std::shared_ptr armory_; - std::shared_ptr trackerClient_; - - std::shared_ptr statusBarView_; - std::shared_ptr sysTrayIcon_; - std::shared_ptr transactionsModel_; - std::shared_ptr portfolioModel_; - std::shared_ptr connectionManager_; - std::shared_ptr celerConnection_; - std::shared_ptr mdProvider_; - std::shared_ptr mdCallbacks_; - std::shared_ptr assetManager_; - std::shared_ptr ccFileManager_; - std::shared_ptr bootstrapDataManager_; - std::shared_ptr authAddrDlg_; - std::shared_ptr signContainer_; - std::shared_ptr autoSignQuoteProvider_; - std::shared_ptr autoSignRFQProvider_; - - std::shared_ptr orderListModel_; - - std::shared_ptr walletsWizard_; - std::shared_ptr utxoReservationMgr_{}; - - QString currentUserLogin_; - - - unsigned armoryReconnectDelay_ = 0; - std::chrono::time_point nextArmoryReconnectAttempt_; - -public slots: - void onReactivate(); - void raiseWindow(); - -private: - struct TxInfo; - - enum class AutoLoginState - { - Idle, - Connecting, - Connected, - Failed, - }; - -private slots: - - void onSend(); - void onGenerateAddress(); - - void openAuthManagerDialog(); - void openConfigDialog(bool showInNetworkPage = false); - void openAccountInfoDialog(); - void openCCTokenDialog(); - - void onZCreceived(const std::vector &); - void showZcNotification(const TxInfo &); - void onNodeStatus(NodeStatus, bool isSegWitEnabled, RpcStatus); - - void onLogin(); - void onLogout(); - - void onCelerConnected(); - void onCelerDisconnected(); - void onCelerConnectionError(int errorCode); - void showRunInBackgroundMessage(); - - void onBsConnectionDisconnected(); - void onBsConnectionFailed(); - - void onInitWalletDialogWasShown(); - -protected: - void closeEvent(QCloseEvent* event) override; - void changeEvent(QEvent* e) override; - -private: - void onUserLoggedIn(); - void onUserLoggedOut(); - - void onAccountTypeChanged(bs::network::UserType userType, bool enabled); - - void setLoginButtonText(const QString& text); - - void setupShortcuts(); - - bool isUserLoggedIn() const; - bool isArmoryConnected() const; - - void InitWidgets(); - - void enableTradingIfNeeded(); - - void showLegacyWarningIfNeeded(); - - void promptSwitchEnv(bool prod); - void switchToTestEnv(); - void switchToProdEnv(); - - void restartTerminal(); - void processDeferredDialogs(); - - std::shared_ptr createClient(); - void activateClient(const std::shared_ptr &bsClient - , const BsClientLoginResult &result, const std::string &email); - const std::string &loginApiKeyEncrypted() const; - void initApiKeyLogins(); - void tryLoginUsingApiKey(); - - void DisplayCreateTransactionDialog(std::shared_ptr dlg); - - void onBootstrapDataLoaded(const std::string& data); - -private: - enum class ChatInitState - { - NoStarted, - InProgress, - Done, - }; - - QString loginButtonText_; - AutoLoginState autoLoginState_{AutoLoginState::Idle}; - QString autoLoginLastErrorMsg_; - std::string loginApiKeyEncrypted_; - QTimer *loginTimer_{}; - std::shared_ptr autoLoginClient_; - - bool initialWalletCreateDialogShown_ = false; - bool deferCCsync_ = false; - - bool wasWalletsRegistered_ = false; - bool walletsSynched_ = false; - bool isArmoryReady_ = false; - - SignContainer::ConnectionError lastSignerError_{}; - - bs::network::BIP15xNewKeyCb cbApproveChat_{ nullptr }; - bs::network::BIP15xNewKeyCb cbApproveProxy_{ nullptr }; - bs::network::BIP15xNewKeyCb cbApproveCcServer_{ nullptr }; - bs::network::BIP15xNewKeyCb cbApproveExtConn_{ nullptr }; - - std::queue> deferredDialogs_; - bool deferredDialogRunning_ = false; - - uint32_t armoryRestartCount_{}; - - class MainWinACT : public ArmoryCallbackTarget - { - public: - MainWinACT(BSTerminalMainWindow *wnd) - : parent_(wnd) {} - ~MainWinACT() override { cleanup(); } - void onZCReceived(const std::string& requestId, const std::vector&) override; - void onStateChanged(ArmoryState) override; - void onTxBroadcastError(const std::string& requestId, const BinaryData &txHash, int errCode - , const std::string &errMsg) override; - void onNodeStatus(NodeStatus, bool isSegWitEnabled, RpcStatus) override; - - private: - BSTerminalMainWindow *parent_; - }; - std::unique_ptr act_; - - std::shared_ptr bsClient_; - - Chat::ChatClientServicePtr chatClientServicePtr_; - - ChatInitState chatInitState_{ChatInitState::NoStarted}; - bool gotChatKeys_{false}; - BinaryData chatTokenData_; - SecureBinaryData chatTokenSign_; - BinaryData chatPubKey_; - SecureBinaryData chatPrivKey_; - - // Default is online to not show online notification after terminal startup - bool isBitcoinCoreOnline_{true}; - - bool accountEnabled_{true}; - - QLockFile &lockFile_; - - bs::network::UserType userType_{}; - - std::shared_ptr tradeSettings_; -}; - -#endif // __BS_TERMINAL_MAIN_WINDOW_H__ diff --git a/BlockSettleUILib/BSTerminalMainWindow.ui b/BlockSettleUILib/BSTerminalMainWindow.ui index 7638a8c3c..f91af01b9 100644 --- a/BlockSettleUILib/BSTerminalMainWindow.ui +++ b/BlockSettleUILib/BSTerminalMainWindow.ui @@ -488,16 +488,6 @@ OVERVIEW
- - - TRADE - - - - - DEALING - - WALLETS @@ -519,16 +509,6 @@ EXPLORER - - - CHARTS - - - - - OTC CHAT - - @@ -561,6 +541,7 @@ + @@ -922,6 +903,11 @@ Open bitcoin URI or payment request + + + Set Delivery Address + + @@ -942,40 +928,15 @@
PortfolioWidget.h
1
- - RFQRequestWidget - QWidget -
Trading/RFQRequestWidget.h
- 1 -
- - RFQReplyWidget - QWidget -
Trading/RFQReplyWidget.h
- 1 -
ExplorerWidget QWidget
ExplorerWidget.h
1
- - ChartWidget - QWidget -
ChartWidget.h
- 1 -
- - ChatWidget - QWidget -
ChatUI/ChatWidget.h
- 1 -
-
diff --git a/BlockSettleUILib/BSTerminalSplashScreen.cpp b/BlockSettleUILib/BSTerminalSplashScreen.cpp index f490ffd78..0186456fb 100644 --- a/BlockSettleUILib/BSTerminalSplashScreen.cpp +++ b/BlockSettleUILib/BSTerminalSplashScreen.cpp @@ -40,11 +40,11 @@ BSTerminalSplashScreen::~BSTerminalSplashScreen() = default; void BSTerminalSplashScreen::SetTipText(const QString& tip) { progress_->setFormat(tip + QString::fromStdString(" %p%")); - QApplication::processEvents(); +// QApplication::processEvents(); } void BSTerminalSplashScreen::SetProgress(int progressValue) { progress_->setValue(progressValue); - QApplication::processEvents(); +// QApplication::processEvents(); } diff --git a/BlockSettleUILib/ButtonMenu.h b/BlockSettleUILib/ButtonMenu.h index 4fbe00bcb..76917ac22 100644 --- a/BlockSettleUILib/ButtonMenu.h +++ b/BlockSettleUILib/ButtonMenu.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -33,4 +33,4 @@ Q_OBJECT QPushButton *parentButton_; }; -#endif // __PUSH_BUTTON_MENU_H__ \ No newline at end of file +#endif // __PUSH_BUTTON_MENU_H__ diff --git a/BlockSettleUILib/CCPortfolioModel.cpp b/BlockSettleUILib/CCPortfolioModel.cpp index 843788957..bff9569b4 100644 --- a/BlockSettleUILib/CCPortfolioModel.cpp +++ b/BlockSettleUILib/CCPortfolioModel.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -417,8 +417,8 @@ class XBTAssetGroupNode : public AssetGroupNode class CCAssetGroupNode : public AssetGroupNode { public: - CCAssetGroupNode(const QString& xbtGroupName, AssetNode* parent) - : AssetGroupNode(xbtGroupName, parent) + CCAssetGroupNode(const QString& groupName, AssetNode* parent) + : AssetGroupNode(groupName, parent) {} ~CCAssetGroupNode() noexcept override = default; @@ -452,8 +452,8 @@ class CCAssetGroupNode : public AssetGroupNode class FXAssetGroupNode : public AssetGroupNode { public: - FXAssetGroupNode(const QString& xbtGroupName, AssetNode* parent) - : AssetGroupNode(xbtGroupName, parent) + FXAssetGroupNode(const QString& groupName, AssetNode* parent) + : AssetGroupNode(groupName, parent) {} ~FXAssetGroupNode() noexcept override = default; @@ -604,6 +604,12 @@ CCPortfolioModel::CCPortfolioModel(const std::shared_ptr(tr("XBT"), tr("Private Shares"), tr("Cash")); +} + int CCPortfolioModel::columnCount(const QModelIndex & parent) const { return PortfolioColumns::PortfolioColumnsCount; @@ -728,14 +734,70 @@ int CCPortfolioModel::rowCount(const QModelIndex & parentIndex) const return getNodeByIndex(parentIndex)->childrenCount(); } -std::shared_ptr CCPortfolioModel::assetManager() +void CCPortfolioModel::onHDWallet(const bs::sync::WalletInfo& wi) +{ + if (!root_->HaveXBTGroup()) { + beginInsertRows({}, rowCount({}), rowCount({})); + root_->GetXBTGroup(); + endInsertRows(); + } + const auto& xbtGroup = root_->GetXBTGroup(); + const auto &displayedWallets = xbtGroup->GetWalletIds(); + const auto& walletId = *wi.ids.cbegin(); + if (displayedWallets.find(walletId) != displayedWallets.end()) { + return; + } + beginInsertRows(createIndex(xbtGroup->getRow(), 0, static_cast(xbtGroup)) + , displayedWallets.size(), displayedWallets.size()); + xbtGroup->AddAsset(QString::fromStdString(wi.name), QString::fromStdString(walletId)); + endInsertRows(); +} + +void CCPortfolioModel::onHDWalletDetails(const bs::sync::HDWalletData& wd) +{ + for (const auto& group : wd.groups) { + if ((group.type != bs::hd::CoinType::Bitcoin_main) && (group.type != bs::hd::CoinType::Bitcoin_test)) { + continue; + } + for (const auto& leaf : group.leaves) { + for (const auto& id : leaf.ids) { + leafIdToRootId_[id] = wd.id; + } + } + } +} + +void CCPortfolioModel::onWalletBalance(const bs::sync::WalletBalanceData& wbd) { - return assetManager_; + leafBalances_[wbd.id] = wbd.balTotal; + const auto& itId = leafIdToRootId_.find(wbd.id); + if (itId != leafIdToRootId_.end()) { + updateWalletBalance(itId->second); + } } -std::shared_ptr CCPortfolioModel::walletsManager() +void CCPortfolioModel::onBalance(const std::string& currency, double balance) { - return walletsManager_; + const auto& fxGroup = root_->GetFXGroup(); + auto fxNode = fxGroup->GetFXNode(currency); + if (!fxNode) { // insert under root + beginInsertRows(QModelIndex{}, fxGroup->getRow(), fxGroup->getRow()); + fxGroup->AddAsset(QString::fromStdString(currency)); + fxNode = fxGroup->GetFXNode(currency); + fxNode->SetFXAmount(balance); + endInsertRows(); + } + else { + fxNode->SetFXAmount(balance); + dataChanged(index(fxGroup->getRow(), PortfolioColumns::XBTValueColumn) + , index(fxGroup->getRow(), PortfolioColumns::XBTValueColumn) + , { Qt::DisplayRole }); + + const auto &parentIndex = createIndex(fxGroup->getRow(), 0, static_cast(fxGroup)); + dataChanged(index(fxNode->getRow(), PortfolioColumns::XBTValueColumn, parentIndex) + , index(fxNode->getRow(), PortfolioColumns::XBTValueColumn, parentIndex) + , { Qt::DisplayRole }); + } } bool CCPortfolioModel::hasChildren(const QModelIndex& parentIndex) const @@ -1022,3 +1084,31 @@ void CCPortfolioModel::updateCCBalance() } } } + +void CCPortfolioModel::updateWalletBalance(const std::string& walletId) +{ + const auto& xbtGroup = root_->GetXBTGroup(); + const auto& parentIndex = createIndex(xbtGroup->getRow(), 0, static_cast(xbtGroup)); + + const auto xbtNode = xbtGroup->GetXBTNode(walletId); + if (!xbtNode) { + return; + } + double balTotal = 0; + for (const auto& idMap : leafIdToRootId_) { + if (idMap.second == walletId) { + try { + balTotal += leafBalances_.at(idMap.first); + } + catch (const std::exception&) {} + } + } + if (xbtNode->SetXBTAmount(balTotal)) { + dataChanged(index(xbtNode->getRow(), PortfolioColumns::XBTValueColumn, parentIndex) + , index(xbtNode->getRow(), PortfolioColumns::XBTValueColumn, parentIndex) + , { Qt::DisplayRole }); + dataChanged(index(xbtGroup->getRow(), PortfolioColumns::XBTValueColumn) + , index(xbtGroup->getRow(), PortfolioColumns::XBTValueColumn) + , { Qt::DisplayRole }); + } +} diff --git a/BlockSettleUILib/CCPortfolioModel.h b/BlockSettleUILib/CCPortfolioModel.h index d2ec30985..31f786eac 100644 --- a/BlockSettleUILib/CCPortfolioModel.h +++ b/BlockSettleUILib/CCPortfolioModel.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,6 +13,7 @@ #include #include +#include "Wallets/SignerDefs.h" namespace bs { namespace sync { @@ -27,9 +28,10 @@ class RootAssetGroupNode; class CCPortfolioModel : public QAbstractItemModel { public: - CCPortfolioModel(const std::shared_ptr & + [[deprecated]] CCPortfolioModel(const std::shared_ptr & , const std::shared_ptr& assetManager , QObject *parent = nullptr); + CCPortfolioModel(QObject* parent = nullptr); ~CCPortfolioModel() noexcept override = default; CCPortfolioModel(const CCPortfolioModel&) = delete; @@ -38,8 +40,10 @@ class CCPortfolioModel : public QAbstractItemModel CCPortfolioModel(CCPortfolioModel&&) = delete; CCPortfolioModel& operator = (CCPortfolioModel&&) = delete; - std::shared_ptr assetManager(); - std::shared_ptr walletsManager(); + void onHDWallet(const bs::sync::WalletInfo&); + void onHDWalletDetails(const bs::sync::HDWalletData&); + void onWalletBalance(const bs::sync::WalletBalanceData&); + void onBalance(const std::string& currency, double balance); private: enum PortfolioColumns @@ -65,25 +69,30 @@ class CCPortfolioModel : public QAbstractItemModel bool hasChildren(const QModelIndex& parent = QModelIndex()) const override; private slots: - void onFXBalanceLoaded(); - void onFXBalanceCleared(); + void onFXBalanceLoaded(); // deprecated + void onFXBalanceCleared(); // deprecated - void onFXBalanceChanged(const std::string& currency); + void onFXBalanceChanged(const std::string& currency); // deprecated - void onXBTPriceChanged(const std::string& currency); - void onCCPriceChanged(const std::string& currency); + void onXBTPriceChanged(const std::string& currency); //deprecated + void onCCPriceChanged(const std::string& currency); // deprecated - void reloadXBTWalletsList(); - void updateXBTBalance(); + void reloadXBTWalletsList(); // deprecated + void updateXBTBalance(); // deprecated - void reloadCCWallets(); - void updateCCBalance(); + void reloadCCWallets(); // deprecated + void updateCCBalance(); // deprecated private: - std::shared_ptr assetManager_; - std::shared_ptr walletsManager_; + void updateWalletBalance(const std::string& walletId); + +private: + [[deprecated]] std::shared_ptr assetManager_; + [[deprecated]] std::shared_ptr walletsManager_; std::shared_ptr root_ = nullptr; + std::map leafIdToRootId_; + std::map leafBalances_; }; #endif // __CC_PORTFOLIO_MODEL__ \ No newline at end of file diff --git a/BlockSettleUILib/CCTokenEntryDialog.cpp b/BlockSettleUILib/CCTokenEntryDialog.cpp deleted file mode 100644 index 457433774..000000000 --- a/BlockSettleUILib/CCTokenEntryDialog.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "CCTokenEntryDialog.h" -#include "ui_CCTokenEntryDialog.h" - -#include "bs_communication.pb.h" - -#include -#include -#include - -#include "BSErrorCodeStrings.h" -#include "BSMessageBox.h" -#include "CCFileManager.h" -#include "SignContainer.h" -#include "Wallets/SyncHDLeaf.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "BSErrorCodeStrings.h" -#include "ApplicationSettings.h" - -namespace { - const auto kAutheIdTimeout = int(BsClient::autheidCcAddressTimeout() / std::chrono::seconds(1)); -} - -CCTokenEntryDialog::CCTokenEntryDialog(const std::shared_ptr &walletsMgr - , const std::shared_ptr &ccFileMgr - , const std::shared_ptr &settings - , QWidget *parent) - : QDialog(parent) - , ui_(new Ui::CCTokenEntryDialog()) - , ccFileMgr_(ccFileMgr) - , walletsMgr_(walletsMgr) - , settings_(settings) -{ - ui_->setupUi(this); - - connect(ui_->pushButtonOk, &QPushButton::clicked, this, &CCTokenEntryDialog::accept); - connect(ui_->pushButtonCancel, &QPushButton::clicked, this, &CCTokenEntryDialog::onCancel); - connect(ui_->lineEditToken, &QLineEdit::textEdited, this, &CCTokenEntryDialog::tokenChanged); - connect(ui_->lineEditToken, &QLineEdit::returnPressed, this, &CCTokenEntryDialog::accept, Qt::QueuedConnection); - - connect(ccFileMgr_.get(), &CCFileManager::CCAddressSubmitted, this, &CCTokenEntryDialog::onCCAddrSubmitted, Qt::QueuedConnection); - connect(ccFileMgr_.get(), &CCFileManager::CCInitialSubmitted, this, &CCTokenEntryDialog::onCCInitialSubmitted, Qt::QueuedConnection); - connect(ccFileMgr_.get(), &CCFileManager::CCSubmitFailed, this, &CCTokenEntryDialog::onCCSubmitFailed, Qt::QueuedConnection); - - updateOkState(); - - ui_->progressBar->setMaximum(kAutheIdTimeout * 10); - - timer_.setInterval(100); - connect(&timer_, &QTimer::timeout, this, &CCTokenEntryDialog::onTimer); - - ui_->stackedWidgetAuth->setCurrentIndex(0); -} - -CCTokenEntryDialog::~CCTokenEntryDialog() = default; - -void CCTokenEntryDialog::tokenChanged() -{ - ui_->labelTokenHint->clear(); - ui_->pushButtonOk->setEnabled(false); - strToken_ = ui_->lineEditToken->text().toStdString(); - if (strToken_.empty()) { - return; - } - - try { - const auto decoded = BtcUtils::base58toScrAddr(strToken_); - Blocksettle::Communication::CCSeedResponse response; - if (!response.ParseFromArray(decoded.getPtr(), decoded.getSize())) { - throw std::invalid_argument("invalid internal token structure"); - } - seed_ = response.bsseed(); - ccProduct_ = response.ccproduct(); - - ccWallet_ = walletsMgr_->getCCWallet(ccProduct_); - if (!ccWallet_) { - ui_->labelTokenHint->setText(tr("The Terminal will prompt you to create the relevant subwallet (%1)," - " if required").arg(QString::fromStdString(ccProduct_))); - - MessageBoxCCWalletQuestion qry(QString::fromStdString(ccProduct_), this); - if (qry.exec() == QDialog::Accepted) { - const auto createCCLeafCb = [this](bs::error::ErrorCode result){ - if (result == bs::error::ErrorCode::TxCancelled) { - reject(); - } - else if (result == bs::error::ErrorCode::NoError) { - ccWallet_ = walletsMgr_->getCCWallet(ccProduct_); - if (ccWallet_) { - ui_->labelTokenHint->setText(tr("Private Market subwallet for %1 created!").arg(QString::fromStdString(ccProduct_))); - } else { - ui_->labelTokenHint->setText(tr("Failed to create CC subwallet %1").arg(QString::fromStdString(ccProduct_))); - } - updateOkState(); - } - else { - ui_->labelTokenHint->setText(tr("Failed to create CC subwallet %1, reason:\n%2") - .arg(QString::fromStdString(ccProduct_)) - .arg(bs::error::ErrorCodeToString(result))); - } - }; - - walletsMgr_->CreateCCLeaf(ccProduct_, createCCLeafCb); - - } else { - reject(); - } - } - } - catch (const std::exception &e) { - ui_->labelTokenHint->setText(tr("Token you entered is not valid: %1").arg(QLatin1String(e.what()))); - } - updateOkState(); -} - -void CCTokenEntryDialog::updateOkState() -{ - ui_->pushButtonOk->setEnabled(ccWallet_ != nullptr); -} - -void CCTokenEntryDialog::accept() -{ - if (!ccWallet_) { - reject(); - return; - } - const auto &cbAddr = [this](const bs::Address &address) { - if (ccFileMgr_->submitAddress(address, seed_, ccProduct_)) { - ui_->pushButtonOk->setEnabled(false); - } else { - onCCSubmitFailed(QString::fromStdString(address.display()) - , tr("Submission to PB failed")); - } - }; - ccWallet_->getNewExtAddress(cbAddr); - - ui_->progressBar->setValue(kAutheIdTimeout * 10); - timeLeft_ = kAutheIdTimeout; - timer_.start(); - ui_->stackedWidgetAuth->setCurrentWidget(ui_->pageAuth); - ui_->labelToken->setText(ui_->lineEditToken->text()); - ui_->labelProduct->setText(QString::fromStdString(ccProduct_)); -} - -void CCTokenEntryDialog::reject() -{ - ccFileMgr_->cancelActiveSign(); - QDialog::reject(); -} - -void CCTokenEntryDialog::onCCAddrSubmitted(const QString addr) -{ - QDialog::accept(); - - const bool isProd = settings_->get(ApplicationSettings::envConfiguration) == - static_cast(ApplicationSettings::EnvConfiguration::Production); - - auto body = tr("BlockSettle will issue your tokens within the next %1.").arg(isProd ? tr("24 hours") : tr("15 minutes")); - if (!isProd) { - body += tr("\n\nOnce mined one block, the tokens can be traded."); - } - - BSMessageBox(BSMessageBox::success, tr("Submission Successful") - , tr("Equity Token Submitted") - , body).exec(); -} - -void CCTokenEntryDialog::onCCInitialSubmitted(const QString addr) -{ - ui_->labelTokenHint->setText(tr("Request was sent")); -} - -void CCTokenEntryDialog::onCCSubmitFailed(const QString, const QString &err) -{ - reject(); - BSMessageBox(BSMessageBox::critical, tr("CC Token submit failure") - , tr("Failed to submit Private Market token to BlockSettle"), err, this).exec(); -} - -void CCTokenEntryDialog::onTimer() -{ - timeLeft_ -= timer_.interval() / 1000.0; - - if (timeLeft_ < 0) { - return; - } - - ui_->progressBar->setValue(int(timeLeft_ * 10)); - ui_->labelTimeLeft->setText(tr("%1 seconds left").arg(int(timeLeft_))); -} - -void CCTokenEntryDialog::onCancel() -{ - setDisabled(true); - timeLeft_ = 0; - timer_.stop(); - reject(); -} diff --git a/BlockSettleUILib/CCTokenEntryDialog.h b/BlockSettleUILib/CCTokenEntryDialog.h deleted file mode 100644 index 28a81b459..000000000 --- a/BlockSettleUILib/CCTokenEntryDialog.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __CC_TOKEN_ENTRY_DIALOG_H__ -#define __CC_TOKEN_ENTRY_DIALOG_H__ - -#include -#include -#include - -#include "BinaryData.h" -#include "BSErrorCode.h" -#include "EncryptionUtils.h" -#include "ValidityFlag.h" - -namespace Ui { - class CCTokenEntryDialog; -} -namespace bs { - namespace sync { - class Wallet; - class WalletsManager; - namespace hd { - class Leaf; - } - } -} -class CCFileManager; -class ApplicationSettings; - -class CCTokenEntryDialog : public QDialog -{ -Q_OBJECT - -public: - CCTokenEntryDialog( - const std::shared_ptr &, - const std::shared_ptr &, - const std::shared_ptr &, - QWidget *parent); - ~CCTokenEntryDialog() override; - -protected: - void accept() override; - void reject() override; - -private slots: - void tokenChanged(); - void updateOkState(); - void onCCAddrSubmitted(const QString addr); - void onCCInitialSubmitted(const QString addr); - void onCCSubmitFailed(const QString addr, const QString &err); - - void onTimer(); - void onCancel(); -private: - std::unique_ptr ui_; - - std::shared_ptr ccFileMgr_; - std::shared_ptr walletsMgr_; - std::shared_ptr ccWallet_; - std::shared_ptr settings_; - - std::string ccProduct_; - std::string strToken_; - uint32_t seed_ = 0; - - QTimer timer_; - double timeLeft_{}; - - ValidityFlag validityFlag_; -}; - -#endif // __CC_TOKEN_ENTRY_DIALOG_H__ diff --git a/BlockSettleUILib/CCTokenEntryDialog.ui b/BlockSettleUILib/CCTokenEntryDialog.ui deleted file mode 100644 index 0441c0529..000000000 --- a/BlockSettleUILib/CCTokenEntryDialog.ui +++ /dev/null @@ -1,548 +0,0 @@ - - - - CCTokenEntryDialog - - - - 0 - 0 - 417 - 175 - - - - - 0 - 0 - - - - Equity Token Issuance - - - false - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 13 - 40 - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - TOKEN ENTRY - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - 0 - 30 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 80 - 0 - - - - - 80 - 16777215 - - - - Equity token - - - true - - - - - - - - - - - - - - - - - - 0 - 0 - - - - Enter token you received from BlockSettle - - - true - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - - - - - - - - - - - 0 - 0 - - - - true - - - - 10 - - - 20 - - - 5 - - - 20 - - - 5 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - Ok - - - true - - - true - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 20 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - EQUITY TOKEN ISSUANCE - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 80 - 0 - - - - - 80 - 16777215 - - - - Token - - - true - - - - - - - TextLabel - - - - - - - Product - - - - - - - TextLabel - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - SIGN WITH AUTH EID - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 6 - - - - - 16777215 - 6 - - - - false - - - - - - - - - - Qt::AlignCenter - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - 0 - 0 - - - - true - - - - 10 - - - 20 - - - 5 - - - 20 - - - 5 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - Cancel - - - true - - - true - - - - - - - - - - - - - - pushButtonOk - - - - diff --git a/BlockSettleUILib/CCWidget.cpp b/BlockSettleUILib/CCWidget.cpp index bf68b3cf3..16460d4aa 100644 --- a/BlockSettleUILib/CCWidget.cpp +++ b/BlockSettleUILib/CCWidget.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -22,28 +22,55 @@ CCWidget::CCWidget(QWidget* parent) , ui_(new Ui::CCWidget()) { ui_->setupUi(this); + ui_->frameXbtValue->hide(); } CCWidget::~CCWidget() = default; void CCWidget::SetPortfolioModel(const std::shared_ptr& model) { - assetManager_ = model->assetManager(); - const auto &walletsManager = model->walletsManager(); - ui_->treeViewCC->setModel(model.get()); ui_->treeViewCC->header()->setSectionResizeMode(QHeaderView::Stretch); connect(model.get(), &CCPortfolioModel::rowsInserted, this, &CCWidget::onRowsInserted); connect(model.get(), &CCPortfolioModel::modelReset, this, [this]() { ui_->treeViewCC->expandAll(); }); - connect(assetManager_.get(), &AssetManager::totalChanged, this, &CCWidget::updateTotalAssets); - connect(walletsManager.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, &CCWidget::updateTotalAssets); - updateTotalAssets(); +} + +void CCWidget::onWalletBalance(const bs::sync::WalletBalanceData& wbd) +{ + walletBalance_[wbd.id] = wbd.balTotal; + updateTotalBalances(); +} + +void CCWidget::onPriceChanged(const std::string& currency, double price) +{ + auto& itPrice = fxPrices_[currency]; + if (itPrice != price) { + itPrice = price; + updateTotalBalances(); + } +} + +void CCWidget::onBasePriceChanged(const std::string ¤cy, double price) +{ + baseCur_ = currency; + if (basePrice_ != price) { + basePrice_ = price; + updateTotalBalances(); + } +} + +void CCWidget::onBalance(const std::string& currency, double balance) +{ + if (balance > 0) { + fxBalance_[currency] = balance; + updateTotalBalances(); + } } void CCWidget::updateTotalAssets() { - auto assets = assetManager_->getTotalAssets(); + int assets = 0; //FIXME if (assets < 0) { ui_->labelTotalValue->setText(tr("%1").arg(tr("Loading..."))); } @@ -58,3 +85,35 @@ void CCWidget::onRowsInserted(const QModelIndex &parent, int first, int last) Q_UNUSED(last) ui_->treeViewCC->expandAll(); } + +void CCWidget::updateTotalBalances() +{ + if (walletBalance_.empty() && fxBalance_.empty()) { + ui_->labelTotalValue->setText(tr("%1").arg(tr("Loading..."))); + } + else { + double xbtBalance = 0; + for (const auto& bal : walletBalance_) { + xbtBalance += bal.second; + } + for (const auto& bal : fxBalance_) { + try { + xbtBalance += bal.second * fxPrices_.at(bal.first); + } + catch (const std::exception&) {} + } + ui_->labelTotalValue->setText(tr("%1").arg(UiUtils::displayAmount(xbtBalance))); + } + + if (!walletBalance_.empty() && !baseCur_.empty()) { + ui_->frameXbtValue->show(); + + double xbtBalance = 0; + for (const auto& bal : walletBalance_) { + xbtBalance += bal.second; + } + xbtBalance *= basePrice_; + ui_->labelXbtValue->setText(UiUtils::UnifyValueString(tr("%1").arg(QString::number(xbtBalance, 'f', 2)))); + ui_->labelBasePrice->setText(UiUtils::UnifyValueString(QString::number(basePrice_, 'f', 2))); + } +} diff --git a/BlockSettleUILib/CCWidget.h b/BlockSettleUILib/CCWidget.h index 83d1a3d63..b46ae1e1f 100644 --- a/BlockSettleUILib/CCWidget.h +++ b/BlockSettleUILib/CCWidget.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,6 +13,7 @@ #include #include +#include "Wallets/SignerDefs.h" namespace Ui { class CCWidget; @@ -31,13 +32,25 @@ Q_OBJECT void SetPortfolioModel(const std::shared_ptr& model); + void onWalletBalance(const bs::sync::WalletBalanceData&); + void onPriceChanged(const std::string& currency, double price); + void onBasePriceChanged(const std::string ¤cy, double price); + void onBalance(const std::string& currency, double balance); + private slots: void updateTotalAssets(); void onRowsInserted(const QModelIndex &parent, int first, int last); private: - std::shared_ptr assetManager_; + void updateTotalBalances(); + +private: std::unique_ptr ui_; + std::map walletBalance_; + std::map fxBalance_; + std::map fxPrices_; + std::string baseCur_; + double basePrice_{ 0 }; }; #endif // __CC_WIDGET_H__ diff --git a/BlockSettleUILib/CCWidget.ui b/BlockSettleUILib/CCWidget.ui index 9fc2e9e0e..aeab83f04 100644 --- a/BlockSettleUILib/CCWidget.ui +++ b/BlockSettleUILib/CCWidget.ui @@ -2,7 +2,7 @@ - - CelerAccountInfoDialog - - - - 0 - 0 - 400 - 300 - - - - Account Information - - - - 15 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - true - - - - 10 - - - 0 - - - 10 - - - 0 - - - 10 - - - - - - 80 - 80 - - - - - 80 - 80 - - - - - - - :/resources/username-white.png - - - false - - - Qt::AlignCenter - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 5 - - - 10 - - - 0 - - - 10 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Username - - - - - - - font-weight: bold; - - - TextLabel - - - true - - - - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 5 - - - 10 - - - 0 - - - 10 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Type - - - - - - - - 75 - true - - - - TextLabel - - - true - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - true - - - - 10 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - - - - - - - - - - - - diff --git a/BlockSettleUILib/ChartWidget.cpp b/BlockSettleUILib/ChartWidget.cpp deleted file mode 100644 index 084ce91cd..000000000 --- a/BlockSettleUILib/ChartWidget.cpp +++ /dev/null @@ -1,1319 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChartWidget.h" - -#include - -#include "ApplicationSettings.h" -#include "Colors.h" -#include "MDCallbacksQt.h" -#include "MarketDataProvider.h" -#include "MdhsClient.h" -#include "PubKeyLoader.h" -#include "market_data_history.pb.h" - -#include "trade_history.pb.h" -#include "ui_ChartWidget.h" - -const QColor BACKGROUND_COLOR = QColor(28, 40, 53); -const QColor FOREGROUND_COLOR = QColor(Qt::white); -const QColor VOLUME_COLOR = QColor(32, 159, 223); - -using namespace Blocksettle::Communication::TradeHistory; - -ComboBoxDelegate::ComboBoxDelegate(QObject* parent) - : QItemDelegate(parent) -{ -} - -void ComboBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - if (index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator")) { - painter->setPen(Qt::gray); - painter->drawLine(option.rect.left(), option.rect.center().y(), option.rect.right(), option.rect.center().y()); - } - else if (index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("parent")) { - QStyleOptionViewItem parentOption = option; - parentOption.state |= QStyle::State_Enabled; - QItemDelegate::paint(painter, parentOption, index); - } - else if (index.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("child")) { - QStyleOptionViewItem childOption = option; - int indent = option.fontMetrics.width(QString(4, QChar::fromLatin1(' '))); - childOption.rect.adjust(indent, 0, 0, 0); - childOption.textElideMode = Qt::ElideNone; - QItemDelegate::paint(painter, childOption, index); - } - else { - QItemDelegate::paint(painter, option, index); - } -} - -QSize ComboBoxDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - QString type = index.data(Qt::AccessibleDescriptionRole).toString(); - if (type == QLatin1String("separator")) - return QSize(0, 10); - return QItemDelegate::sizeHint(option, index); -} - -ChartWidget::ChartWidget(QWidget* pParent) - : QWidget(pParent) - , ui_(new Ui::ChartWidget) - , candlesticksChart_(nullptr) - , volumeChart_(nullptr) - , volumeAxisRect_(nullptr) - , lastHigh_(0.0) - , lastLow_(0.0) - , lastClose_(0.0) - , currentTimestamp_(0) - , lastInterval_(-1) - , dragY_(0) - , isDraggingYAxis_(false) -{ - ui_->setupUi(this); - horLine = new QCPItemLine(ui_->customPlot); - vertLine = new QCPItemLine(ui_->customPlot); - setAutoScaleBtnColor(); - connect(ui_->autoScaleBtn, &QPushButton::clicked, this, &ChartWidget::OnAutoScaleBtnClick); - - setMouseTracking(true); - connect(ui_->resetBtn, &QPushButton::clicked, this, &ChartWidget::OnResetBtnClick); - // setting up date range radio button group - dateRange_.addButton(ui_->btn1h, Interval::OneHour); - dateRange_.addButton(ui_->btn6h, Interval::SixHours); - dateRange_.addButton(ui_->btn12h, Interval::TwelveHours); - dateRange_.addButton(ui_->btn24h, Interval::TwentyFourHours); - dateRange_.addButton(ui_->btn1w, Interval::OneWeek); - dateRange_.addButton(ui_->btn1m, Interval::OneMonth); - dateRange_.addButton(ui_->btn6m, Interval::SixMonths); - dateRange_.addButton(ui_->btn1y, Interval::OneYear); - connect(&dateRange_, qOverload(&QButtonGroup::buttonClicked), - this, &ChartWidget::OnDateRangeChanged); - - cboModel_ = new QStandardItemModel(this); - ui_->cboInstruments->setItemDelegate(new ComboBoxDelegate); - ui_->cboInstruments->setModel(cboModel_); - - //uncomment when there will we enought data - ui_->btn1y->hide(); - ui_->btn6m->hide(); -} - -void ChartWidget::init(const std::shared_ptr& appSettings - , const std::shared_ptr& mdProvider - , const std::shared_ptr &mdCallbacks - , const std::shared_ptr& connectionManager - , const std::shared_ptr& logger) -{ - auto env = static_cast( - appSettings->get(ApplicationSettings::envConfiguration)); - const auto &mdhsHost = PubKeyLoader::serverHostName(PubKeyLoader::KeyType::Mdhs, env); - const auto &mdhsPort = PubKeyLoader::serverHttpsPort(); - - appSettings_ = appSettings; - mdProvider_ = mdProvider; - mdhsClient_ = std::make_shared(connectionManager, logger, mdhsHost, mdhsPort); - logger_ = logger; - - connect(mdhsClient_.get(), &MdhsClient::DataReceived, this, &ChartWidget::OnDataReceived); - - connect(ui_->pushButtonMDConnection, &QPushButton::clicked, this, &ChartWidget::ChangeMDSubscriptionState); - - connect(ui_->cboInstruments, &QComboBox::currentTextChanged, this, &ChartWidget::OnInstrumentChanged); - ui_->cboInstruments->setEnabled(false); - - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, this, &ChartWidget::OnMdUpdated); - connect(mdCallbacks.get(), &MDCallbacksQt::OnNewFXTrade, this, &ChartWidget::OnNewXBTorFXTrade); - connect(mdCallbacks.get(), &MDCallbacksQt::OnNewPMTrade, this, &ChartWidget::OnNewPMTrade); - connect(mdCallbacks.get(), &MDCallbacksQt::OnNewXBTTrade, this, &ChartWidget::OnNewXBTorFXTrade); - connect(mdCallbacks.get(), &MDCallbacksQt::WaitingForConnectionDetails, this, &ChartWidget::OnLoadingNetworkSettings); - connect(mdCallbacks.get(), &MDCallbacksQt::StartConnecting, this, &ChartWidget::OnMDConnecting); - connect(mdCallbacks.get(), &MDCallbacksQt::Connected, this, &ChartWidget::OnMDConnected); - connect(mdCallbacks.get(), &MDCallbacksQt::Disconnecting, this, &ChartWidget::OnMDDisconnecting); - connect(mdCallbacks.get(), &MDCallbacksQt::Disconnected, this, &ChartWidget::OnMDDisconnected); - - // initialize charts - InitializeCustomPlot(); - - auto timeframe = appSettings_->get(ApplicationSettings::ChartTimeframe).toInt(); - const auto btns = dateRange_.buttons(); - for (auto it : btns) { - if (dateRange_.id(it) == timeframe) { - it->setChecked(true); - break; - } - } -} - -void ChartWidget::setAuthorized(bool authorized) -{ - ui_->pushButtonMDConnection->setEnabled(!authorized); - authorized_ = authorized; -} - -void ChartWidget::disconnect() -{ - OnMDDisconnecting(); -} - -ChartWidget::~ChartWidget() -{ - delete ui_; -} - -void ChartWidget::SendEoDRequest() -{ - OhlcRequest ohlcRequest; - ohlcRequest.set_product(getCurrentProductName().toStdString()); - ohlcRequest.set_interval(static_cast(dateRange_.checkedId())); - ohlcRequest.set_count(1); - ohlcRequest.set_lesser_then(-1); - - MarketDataHistoryRequest request; - request.set_request_type(MarketDataHistoryMessageType::EoDPriceType); - request.set_request(ohlcRequest.SerializeAsString()); - mdhsClient_->SendRequest(request); - eodRequestSent_ = true; -} - -// Populate combo box with existing instruments comeing from mdProvider -void ChartWidget::OnMdUpdated(bs::network::Asset::Type assetType, const QString& security, - bs::network::MDFields mdFields) -{ - if ((assetType == bs::network::Asset::Undefined) && security.isEmpty()) // Celer disconnected - { - isProductListInitialized_ = false; - cboModel_->clear(); - ui_->cboInstruments->setEnabled(false); - return; - } - if (!isProductListInitialized_) { - isProductListInitialized_ = true; - MarketDataHistoryRequest request; - request.set_request_type(MarketDataHistoryMessageType::ProductsListType); - mdhsClient_->SendRequest(request); - return; - } - - if (assetType == bs::network::Asset::Type::PrivateMarket) { - const std::string& productName = security.toStdString(); - - if (pmProducts_.find(productName) == pmProducts_.end()) { - pmProducts_.emplace(productName); - QTimer::singleShot(5000, [this]() - { - MarketDataHistoryRequest request; - request.set_request_type(MarketDataHistoryMessageType::ProductsListType); - mdhsClient_->SendRequest(request); - }); - } - } - - for (const auto& field : mdFields) { - if (field.type == bs::network::MDField::MDTimestamp) { - if (currentTimestamp_ >= field.value) { - break; - } - - currentTimestamp_ = field.value; - CheckToAddNewCandle(currentTimestamp_); - auto date = QDateTime::fromMSecsSinceEpoch(currentTimestamp_, Qt::TimeSpec::UTC).time(); - if (!eodUpdated_ - && !eodRequestSent_ - && date.hour() == 0 - && date.minute() == 0 - && date.second() > 5 - ) { - SendEoDRequest(); - QTimer::singleShot(5000, [this]() - { - if (!eodUpdated_) { - SendEoDRequest(); - } - }); - } - if (date.hour() != 0) { - eodUpdated_ = false; - eodRequestSent_ = false; - } - - break; - } - } -} - -void ChartWidget::UpdateChart(const int& interval) -{ - eodUpdated_ = false; - eodRequestSent_ = false; - auto product = getCurrentProductName(); - if (product.isEmpty()) - return; - if (!candlesticksChart_ || !volumeChart_) { - return; - } - candlesticksChart_->data()->clear(); - volumeChart_->data()->clear(); - qreal width = 0.8 * IntervalWidth(interval) / 1000; - candlesticksChart_->setWidth(width); - volumeChart_->setWidth(width); - OhlcRequest ohlcRequest; - ohlcRequest.set_product(product.toStdString()); - ohlcRequest.set_interval(static_cast(interval)); - ohlcRequest.set_count(requestLimit); - ohlcRequest.set_lesser_then(-1); - - MarketDataHistoryRequest request; - request.set_request_type(MarketDataHistoryMessageType::OhlcHistoryType); - request.set_request(ohlcRequest.SerializeAsString()); - mdhsClient_->SendRequest(request); -} - -void ChartWidget::OnDataReceived(const std::string& data) -{ - if (data.empty()) { - logger_->error("Empty data received from mdhs."); - return; - } - - MarketDataHistoryResponse response; - if (!response.ParseFromString(data)) { - logger_->error("can't parse response from mdhs: {}", data); - return; - } - - switch (response.response_type()) { - case MarketDataHistoryMessageType::ProductsListType: - ProcessProductsListResponse(response.response()); - break; - case MarketDataHistoryMessageType::OhlcHistoryType: - ProcessOhlcHistoryResponse(response.response()); - break; - case MarketDataHistoryMessageType::EoDPriceType: - { - ProcessEodResponse(response.response()); - } - break; - default: - logger_->error("[ApiServerConnectionListener::OnDataReceived] undefined message type"); - break; - } -} - -void ChartWidget::ProcessProductsListResponse(const std::string& data) -{ - if (data.empty()) { - logger_->error("Empty data received from mdhs."); - return; - } - - ProductsListResponse response; - if (!response.ParseFromString(data)) { - logger_->error("can't parse response from mdhs: {}", data); - return; - } - - cboModel_->clear(); - - std::map, std::greater<>> tempMap; - for (const auto& product : response.products()) { - tempMap[product.type()].push_back(product.product()); - productTypesMapper[product.product()] = product.type(); - - if (product.type() == TradeHistoryTradeType::PMTradeType) { - pmProducts_.emplace(product.product()); - } - } - for (const auto& mapElement : tempMap) { - AddParentItem(cboModel_, ProductTypeToString(mapElement.first)); - for (const auto& name : mapElement.second) { - AddChildItem(cboModel_, QString::fromStdString(name)); - } - } - - auto savedProduct = appSettings_->get(ApplicationSettings::ChartProduct).toString(); - bool found = false; - for (int i = 0; i < ui_->cboInstruments->count(); i++) { - if (ui_->cboInstruments->itemText(i).contains(savedProduct)) { - ui_->cboInstruments->setCurrentIndex(i); - found = true; - break; - } - } - if (!found) { - ui_->cboInstruments->setCurrentIndex(1); //to prevent automatic selection of parent item - } - - zoomDiff_ = 0.0; - - ui_->cboInstruments->setEnabled(true); -} - -void ChartWidget::ProcessOhlcHistoryResponse(const std::string& data) -{ - if (data.empty()) { - logger_->error("Empty data received from mdhs."); - return; - } - - OhlcResponse response; - if (!response.ParseFromString(data)) { - logger_->error("can't parse response from mdhs: {}", data); - return; - } - - bool firstPortion = candlesticksChart_->data()->size() == 0; - - auto product = getCurrentProductName(); - auto interval = dateRange_.checkedId(); - - if (product != QString::fromStdString(response.product()) || interval != response.interval()) - return; - - quint64 maxTimestamp = 0; - - for (int i = 0; i < response.candles_size(); i++) { - auto candle = response.candles(i); - maxTimestamp = qMax(maxTimestamp, static_cast(candle.timestamp())); - - bool isLast = (i == 0); - if (candle.timestamp() >= lastCandle_.timestamp() - || lastCandle_.timestamp() - candle.timestamp() < IntervalWidth( interval, 1, QDateTime::fromMSecsSinceEpoch(candle.timestamp(), Qt::TimeSpec::UTC))) { - if (lastCandle_.timestamp() != 0) { - logger_->error("Invalid distance between candles from mdhs. The last timestamp: {} new timestamp: {}", - lastCandle_.timestamp(), candle.timestamp()); - } - } - else { - if (lastCandle_.timestamp() - candle.timestamp() != IntervalWidth( - interval, 1, QDateTime::fromMSecsSinceEpoch(candle.timestamp(), Qt::TimeSpec::UTC)) && candlesticksChart_->data()->size()) { - for (int j = 0; j < (lastCandle_.timestamp() - candle.timestamp()) / IntervalWidth( - interval, 1, QDateTime::fromMSecsSinceEpoch(candle.timestamp(), Qt::TimeSpec::UTC)) - 1; j++) { - AddDataPoint(candle.close(), candle.close(), candle.close(), candle.close(), - lastCandle_.timestamp() - IntervalWidth(interval) * (j + 1), 0); - } - } - } - - lastCandle_ = candle; - - AddDataPoint(candle.open(), candle.high(), candle.low(), candle.close(), candle.timestamp(), candle.volume()); -#if 0 - qDebug("Added: %s, open: %f, high: %f, low: %f, close: %f, volume: %f" - , QDateTime::fromMSecsSinceEpoch(candle.timestamp(), Qt::TimeSpec::UTC) - .toUTC().toString(Qt::ISODateWithMs).toStdString().c_str() - , candle.open() - , candle.high() - , candle.low() - , candle.close() - , candle.volume()); -#endif - if (firstPortion && isLast) { - lastHigh_ = candle.high(); - lastLow_ = candle.low(); - lastClose_ = candle.close(); - } - } - - if (firstPortion) { - if (!qFuzzyIsNull(currentTimestamp_)) { - newestCandleTimestamp_ = GetCandleTimestamp(currentTimestamp_, static_cast(interval)); - } - else { - logger_->warn("Data from mdhs came before MD update, or MD send wrong current timestamp"); - newestCandleTimestamp_ = GetCandleTimestamp(QDateTime::currentDateTimeUtc().toMSecsSinceEpoch(), - static_cast(interval)); - } - if (!response.candles_size()) { - AddDataPoint(0, 0, 0, 0, newestCandleTimestamp_, 0); - maxTimestamp = newestCandleTimestamp_; - } - else { - if (newestCandleTimestamp_ > maxTimestamp) { - auto lastCandle = *(candlesticksChart_->data()->at(candlesticksChart_->data()->size() - 1)); - for (quint64 i = 0; i < (newestCandleTimestamp_ - maxTimestamp) / IntervalWidth(interval); i++) { - AddDataPoint(lastCandle.close, lastCandle.close, lastCandle.close, lastCandle.close, - newestCandleTimestamp_ - IntervalWidth(interval) * i, 0); - } - maxTimestamp = newestCandleTimestamp_; - } - } - firstTimestampInDb_ = response.first_stamp_in_db() / 1000; - UpdatePlot(interval, maxTimestamp); - } - else { - LoadAdditionalPoints(volumeAxisRect_->axis(QCPAxis::atBottom)->range()); - rescalePlot(); - ui_->customPlot->replot(); - } -} - -void ChartWidget::ProcessEodResponse(const std::string& data) -{ - eodRequestSent_ = false; - EodPrice eodPrice; - eodPrice.ParseFromString(data); - if (getCurrentProductName().toStdString() != eodPrice.product()) { - return; - } - if (candlesticksChart_->data()->size() < 2) { - return; - } - auto delta = dateRange_.checkedId() >= Interval::TwentyFourHours ? 2 : 1; //should we update last or pre-last candle - auto lastCandle = candlesticksChart_->data()->end() - delta; - lastCandle->high = qMax(lastCandle->high, eodPrice.price()); - lastCandle->low = qMin(lastCandle->low, eodPrice.price()); - if (!qFuzzyCompare(lastCandle->close, eodPrice.price())) { - lastCandle->close = eodPrice.price(); - UpdateOHLCInfo(IntervalWidth(dateRange_.checkedId()) / 1000, - ui_->customPlot->xAxis->pixelToCoord(ui_->customPlot->mapFromGlobal(QCursor::pos()).x())); - rescalePlot(); - ui_->customPlot->replot(); - } - eodUpdated_ = true; -} - -double ChartWidget::CountOffsetFromRightBorder() -{ - return ui_->customPlot->xAxis->pixelToCoord(6) - ui_->customPlot->xAxis->pixelToCoord(0); -} - -void ChartWidget::CheckToAddNewCandle(qint64 stamp) -{ - if (stamp <= newestCandleTimestamp_ + IntervalWidth(dateRange_.checkedId()) || !volumeChart_->data()->size()) { - return; - } - auto candleStamp = GetCandleTimestamp(stamp, static_cast(dateRange_.checkedId())); - auto lastCandle = *(candlesticksChart_->data()->at(candlesticksChart_->data()->size() - 1)); - for (quint64 i = 0; i < (candleStamp - newestCandleTimestamp_) / IntervalWidth(dateRange_.checkedId()); i++) { - AddDataPoint(lastCandle.close, lastCandle.close, lastCandle.close, lastCandle.close, - candleStamp - IntervalWidth(dateRange_.checkedId()) * i, 0); - } - newestCandleTimestamp_ = candleStamp; - auto upper = ui_->customPlot->xAxis->range().upper; - if (upper + IntervalWidth(dateRange_.checkedId()) / 1000 > newestCandleTimestamp_ / 1000) { - ui_->customPlot->xAxis->moveRange(IntervalWidth(dateRange_.checkedId()) / 1000); - } - AddDataPoint(lastClose_, lastClose_, lastClose_, lastClose_, newestCandleTimestamp_, 0); - ui_->customPlot->replot(); -} - -void ChartWidget::setAutoScaleBtnColor() const -{ - QString color = QStringLiteral("background-color: transparent; border: none; color: %1"). - arg(autoScaling_ ? QStringLiteral("rgb(36,124,172)") : QStringLiteral("rgb(255, 255, 255)")); - ui_->autoScaleBtn->setStyleSheet(color); -} - -void ChartWidget::DrawCrossfire(QMouseEvent* event) -{ - vertLine->start->setCoords(qMin(event->pos().x(), volumeAxisRect_->right() + 1), 0); - vertLine->end->setCoords(qMin(event->pos().x(), volumeAxisRect_->right() + 1), volumeAxisRect_->bottom()); - horLine->start->setCoords(0, qMin(event->pos().y(), volumeAxisRect_->bottom())); - horLine->end->setCoords(volumeAxisRect_->right(), qMin(event->pos().y(), volumeAxisRect_->bottom())); - vertLine->setVisible(true); - horLine->setVisible(true); -} - -void ChartWidget::UpdatePrintFlag() -{ - if (candlesticksChart_->data()->isEmpty()) { - lastPrintFlag_->setVisible(false); - return; - } - lastPrintFlag_->setVisible(true); - if (isHigh_) { - lastPrintFlag_->setBrush(QBrush(c_greenColor)); - } else { - lastPrintFlag_->setBrush(QBrush(c_redColor)); - } - auto prec = FractionSizeForProduct(productTypesMapper[getCurrentProductName().toStdString()]); - lastPrintFlag_->setText(QStringLiteral("- ") + QString::number(lastClose_, 'f', prec)); - lastPrintFlag_->position->setCoords(ui_->customPlot->yAxis2->axisRect()->rect().right() + 2, ui_->customPlot->yAxis2->coordToPixel(lastClose_)); - ui_->customPlot->replot(); -} - -void ChartWidget::UpdatePlot(const int& interval, const qint64& timestamp) -{ - qreal upper = timestamp / 1000 + IntervalWidth(interval) / 1000 / 2; - qreal lower = upper - - IntervalWidth(dateRange_.checkedId(), appSettings_->get(ApplicationSettings::ChartCandleCount).toInt()) / 1000 - - IntervalWidth(interval) / 1000 / 2; - ui_->customPlot->xAxis->setRange(lower, upper); - auto margin = IntervalWidth(dateRange_.checkedId()) / 1000 * 0.5; - ui_->customPlot->xAxis->setRange(lower - margin, upper + margin); - rescaleCandlesYAxis(); - ui_->customPlot->yAxis2->setNumberPrecision( - FractionSizeForProduct(productTypesMapper[getCurrentProductName().toStdString()])); - ui_->customPlot->replot(); - UpdatePrintFlag(); -} - -bool ChartWidget::needLoadNewData(const QCPRange& range, const QSharedPointer data) const -{ - return data->size() && - (range.lower - data->constBegin()->key < IntervalWidth(dateRange_.checkedId()) / 1000 * loadDistance) - && firstTimestampInDb_ + IntervalWidth(OneHour) < data->constBegin()->key; -} - -void ChartWidget::LoadAdditionalPoints(const QCPRange& range) -{ - const auto data = candlesticksChart_->data(); - if (needLoadNewData(range, data)) { - if (qFuzzyCompare(prevRequestStamp, data->constBegin()->key)) { - return; - } - OhlcRequest ohlcRequest; - auto product = getCurrentProductName(); - ohlcRequest.set_product(product.toStdString()); - ohlcRequest.set_interval(static_cast(dateRange_.checkedId())); - ohlcRequest.set_count(requestLimit); - ohlcRequest.set_lesser_then(data->constBegin()->key * 1000); - - prevRequestStamp = data->constBegin()->key; - - MarketDataHistoryRequest request; - request.set_request_type(MarketDataHistoryMessageType::OhlcHistoryType); - request.set_request(ohlcRequest.SerializeAsString()); - mdhsClient_->SendRequest(request); - } -} - -void ChartWidget::pickTicketDateFormat(const QCPRange& range) const -{ - const float rangeCoeff = 0.8; - if (range.size() < 3 * 24 * 60 * 60 * rangeCoeff) { - dateTimeTicker->setDateTimeFormat(QStringLiteral("dd MMM\nHH:mm")); - } - else if (range.size() < 365 * 24 * 60 * 60 * rangeCoeff) { - dateTimeTicker->setDateTimeFormat(QStringLiteral("dd MMM\n")); - } - else { - dateTimeTicker->setDateTimeFormat(QStringLiteral("MMM yyyy\n")); - } -} - -QString ChartWidget::getCurrentProductName() const -{ - return ui_->cboInstruments->currentText().simplified().replace(QStringLiteral(" "), QStringLiteral("")); -} - -void ChartWidget::AddParentItem(QStandardItemModel* model, const QString& text) -{ - QStandardItem* item = new QStandardItem(text); - item->setFlags(item->flags() & ~(Qt::ItemIsEnabled | Qt::ItemIsSelectable)); - item->setData(QStringLiteral("parent"), Qt::AccessibleDescriptionRole); - QFont font = item->font(); - font.setBold(true); - item->setFont(font); - model->appendRow(item); -} - -void ChartWidget::AddChildItem(QStandardItemModel* model, const QString& text) -{ - QStandardItem* item = new QStandardItem(text + QString(4, QChar::fromLatin1(' '))); - item->setData(QStringLiteral("child"), Qt::AccessibleDescriptionRole); - model->appendRow(item); -} - -void ChartWidget::AddDataPoint(const qreal& open, const qreal& high, const qreal& low, const qreal& close, - const qreal& timestamp, const qreal& volume) const -{ - if (candlesticksChart_) { - candlesticksChart_->data()->add(QCPFinancialData(timestamp / 1000, open, high, low, close)); - } - if (volumeChart_) { - volumeChart_->data()->add(QCPBarsData(timestamp / 1000, volume)); - } -} - -quint64 ChartWidget::IntervalWidth(int interval, int count, const QDateTime& specialDate) const -{ - if (interval == -1) { - return 1; - } - qreal hour = 3600000; - switch (static_cast(interval)) { - case Interval::OneYear: - return hour * (specialDate.isValid() ? specialDate.date().daysInYear() * 24 : 8760) * count; - case Interval::SixMonths: - return hour * (specialDate.isValid() ? 24 * specialDate.date().daysInMonth() * 6 : 4320) * count; - case Interval::OneMonth: - return hour * (specialDate.isValid() ? 24 * specialDate.date().daysInMonth() : 720) * count; - case Interval::OneWeek: - return hour * 168 * count; - case Interval::TwentyFourHours: - return hour * 24 * count; - case Interval::TwelveHours: - return hour * 12 * count; - case Interval::SixHours: - return hour * 6 * count; - case Interval::OneHour: - return hour * count; - default: - return hour * count; - } -} - -int ChartWidget::FractionSizeForProduct(TradeHistoryTradeType type) -{ - switch (type) { - case FXTradeType: - return 4; - case XBTTradeType: - return 2; - case PMTradeType: - return 6; - default: - return -1; - } -} - -// Handles changes of date range. -void ChartWidget::OnDateRangeChanged(int interval) -{ - if (lastInterval_ != interval) { - appSettings_->set(ApplicationSettings::ChartTimeframe, interval); - lastInterval_ = interval; - zoomDiff_ = 0.0; - UpdateChart(interval); - } -} - -void ChartWidget::OnInstrumentChanged(const QString& text) -{ - if (text != getCurrentProductName()) { - appSettings_->set(ApplicationSettings::ChartProduct, getCurrentProductName()); - zoomDiff_ = volumeAxisRect_->axis(QCPAxis::atBottom)->range().size(); - isHigh_ = true; - UpdateChart(dateRange_.checkedId()); - } -} - -QString ChartWidget::GetFormattedStamp(double timestamp) -{ - QString resultFormat; - switch (static_cast(dateRange_.checkedId())) { - case TwelveHours: - case SixHours: - case OneHour: - resultFormat = QStringLiteral("dd MMM yy hh:mm"); - break; - default: - resultFormat = QStringLiteral("dd MMM yy"); - } - return QDateTime::fromSecsSinceEpoch(qint64(timestamp)).toUTC().toString(resultFormat); -} - -void ChartWidget::UpdateOHLCInfo(double width, double timestamp) -{ - auto ohlcValue = *candlesticksChart_->data()->findBegin(timestamp + width / 2); - auto volumeValue = *volumeChart_->data()->findBegin(timestamp + width / 2); - //ohlcValue.close >= ohlcValue.open ? c_greenColor : c_redColor - const auto& color = VOLUME_COLOR.name(); - auto prec = FractionSizeForProduct(productTypesMapper[getCurrentProductName().toStdString()]); - QString partForm = QStringLiteral("%1"); - QString format = QStringLiteral( -"  %1     %2 %3   %4 %5   %6 %7   %8 %9   %10 %11" -) - .arg(partForm.arg(GetFormattedStamp(ohlcValue.key)).arg(FOREGROUND_COLOR.name())) - .arg(partForm.arg(QStringLiteral("O:")).arg(FOREGROUND_COLOR.name())) - .arg(partForm.arg(ohlcValue.open, 0, 'f', prec).arg(color)) - .arg(partForm.arg(QStringLiteral("H:")).arg(FOREGROUND_COLOR.name())) - .arg(partForm.arg(ohlcValue.high, 0, 'f', prec).arg(color)) - .arg(partForm.arg(QStringLiteral("L:")).arg(FOREGROUND_COLOR.name())) - .arg(partForm.arg(ohlcValue.low, 0, 'f', prec).arg(color)) - .arg(partForm.arg(QStringLiteral("C:")).arg(FOREGROUND_COLOR.name())) - .arg(partForm.arg(ohlcValue.close, 0, 'f', prec).arg(color)) - .arg(partForm.arg(QStringLiteral("Volume:")).arg(FOREGROUND_COLOR.name())) - .arg(partForm.arg(volumeValue.value).arg(color)); - ui_->ohlcLbl->setText(format); -} - -void ChartWidget::OnPlotMouseMove(QMouseEvent* event) -{ - DrawCrossfire(event); - - double x = event->localPos().x(); - double width = IntervalWidth(dateRange_.checkedId()) / 1000; - double timestamp = ui_->customPlot->xAxis->pixelToCoord(x); - if (!candlesticksChart_->data()->size() || - timestamp > candlesticksChart_->data()->at(candlesticksChart_->data()->size() - 1)->key + width / 2 || - timestamp < candlesticksChart_->data()->at(0)->key - width / 2) { - ui_->ohlcLbl->setText({}); - } - else { - UpdateOHLCInfo(width, timestamp); - } - - if (isDraggingYAxis_) { - auto rightAxis = ui_->customPlot->yAxis2; - auto currentYPos = event->pos().y(); - auto lower_bound = rightAxis->range().lower; - auto upper_bound = rightAxis->range().upper; - auto diff = upper_bound - lower_bound; - auto directionCoeff = (currentYPos - lastDragCoord_.y() > 0) ? -1 : 1; - lastDragCoord_.setY(currentYPos); - double tempCoeff = 28.0; //change this to impact on xAxis scale speed, the lower coeff the faster scaling - upper_bound -= diff / tempCoeff * directionCoeff; - lower_bound += diff / tempCoeff * directionCoeff; - rightAxis->setRange(lower_bound, upper_bound); - } - if (isDraggingXAxis_) { - auto bottomAxis = volumeAxisRect_->axis(QCPAxis::atBottom); - auto currentXPos = event->pos().x(); - auto lower_bound = volumeAxisRect_->axis(QCPAxis::atBottom)->range().lower; - auto upper_bound = volumeAxisRect_->axis(QCPAxis::atBottom)->range().upper; - auto diff = upper_bound - lower_bound; - auto directionCoeff = (currentXPos - lastDragCoord_.x() > 0) ? -1 : 1; - //double scalingCoeff = qAbs(currentXPos - startDragCoordX_) / ui_->customPlot->size().width(); - lastDragCoord_.setX(currentXPos); - double tempCoeff = 10.0; //change this to impact on xAxis scale speed, the lower coeff the faster scaling - lower_bound += diff / tempCoeff * /*scalingCoeff * */ directionCoeff; - auto lower_limit = candlesticksChart_->data()->constBegin()->key - (upper_bound - lower_bound) * 0.2; - if (lower_bound < lower_limit && directionCoeff == -1) { - return; - } - bottomAxis->setRange(lower_bound, upper_bound); - } - if (isDraggingMainPlot_) { - auto axis = ui_->customPlot->xAxis; - const double startPixel = dragStartPos_.x(); - const double currentPixel = event->pos().x(); - const double diff = axis->pixelToCoord(startPixel) - axis->pixelToCoord(currentPixel); - auto size = candlesticksChart_->data()->size(); - double upper_bound = size ? candlesticksChart_->data()->at(size - 1)->key : QDateTime::currentSecsSinceEpoch(); - upper_bound += IntervalWidth(dateRange_.checkedId()) / 1000 / 2 + CountOffsetFromRightBorder(); - double lower_bound = QDateTime(QDate(2009, 1, 3)).toSecsSinceEpoch(); - if (dragStartRangeX_.upper + diff > upper_bound && diff > 0) { - dragStartPos_.setX(event->pos().x()); - dragStartRangeX_ = axis->range(); - } - else if (dragStartRangeX_.lower + diff < lower_bound && diff < 0) { - dragStartPos_.setX(event->pos().x()); - dragStartRangeX_ = axis->range(); - } - else { - axis->setRange(dragStartRangeX_.lower + diff, dragStartRangeX_.upper + diff); - } - if (!autoScaling_) { - auto axisY = ui_->customPlot->yAxis2; - const double startPixelY = dragStartPos_.y(); - const double currentPixelY = event->pos().y(); - const double diffY = axisY->pixelToCoord(startPixelY) - axisY->pixelToCoord(currentPixelY); - axisY->setRange(dragStartRangeY_.lower + diffY, dragStartRangeY_.upper + diffY); - } - - } - ui_->customPlot->replot(); -} - -void ChartWidget::leaveEvent(QEvent* event) -{ - vertLine->setVisible(false); - horLine->setVisible(false); - ui_->customPlot->replot(); -} - -void ChartWidget::rescaleCandlesYAxis() -{ - bool foundRange = false; - auto keyRange = candlesticksChart_->keyAxis()->range(); - keyRange.upper += IntervalWidth(dateRange_.checkedId()) / 1000 / 2; - keyRange.lower -= IntervalWidth(dateRange_.checkedId()) / 1000 / 2; - auto newRange = candlesticksChart_->getValueRange(foundRange, QCP::sdBoth, keyRange); - if (foundRange) { - const double margin = 0.15; - if (!QCPRange::validRange(newRange)) // likely due to range being zero - { - double center = (newRange.lower + newRange.upper) * 0.5; - // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason - newRange.lower = center - candlesticksChart_->valueAxis()->range().size() * margin / 2.0; - newRange.upper = center + candlesticksChart_->valueAxis()->range().size() * margin / 2.0; - } - else { - auto old = candlesticksChart_->valueAxis()->range(); - if (old != newRange) { - newRange.lower -= newRange.size() * margin; - newRange.upper += newRange.size() * margin; - } - } - candlesticksChart_->valueAxis()->setRange(newRange); - } -} - -void ChartWidget::rescaleVolumesYAxis() const -{ - if (!volumeChart_->data()->size()) { - return; - } - auto lower_bound = volumeAxisRect_->axis(QCPAxis::atBottom)->range().lower; - auto upper_bound = volumeAxisRect_->axis(QCPAxis::atBottom)->range().upper; - double maxVolume = volumeChart_->data()->constBegin()->value; - for (const auto& it : *volumeChart_->data()) { - if (it.key >= lower_bound && it.key <= upper_bound) { - maxVolume = qMax(maxVolume, it.value); - } - } - if (!qFuzzyCompare(maxVolume, volumeAxisRect_->axis(QCPAxis::atBottom)->range().upper)) { - volumeAxisRect_->axis(QCPAxis::atRight)->setRange(0, maxVolume); - ui_->customPlot->replot(); - } -} - -void ChartWidget::rescalePlot() -{ - if (autoScaling_) { - rescaleCandlesYAxis(); - } - rescaleVolumesYAxis(); -} - -void ChartWidget::OnMousePressed(QMouseEvent* event) -{ - auto select = ui_->customPlot->yAxis2->selectTest(event->pos(), false); - isDraggingYAxis_ = select != -1.0; - if (isDraggingYAxis_) { - if (autoScaling_) { - ui_->autoScaleBtn->animateClick(); - } - } - - auto selectXPoint = volumeAxisRect_->axis(QCPAxis::atBottom)->selectTest(event->pos(), false); - isDraggingXAxis_ = selectXPoint != -1.0; - if (isDraggingXAxis_) { - volumeAxisRect_->axis(QCPAxis::atBottom)->axisRect()->setRangeDrag( - volumeAxisRect_->axis(QCPAxis::atBottom)->orientation()); - startDragCoordX_ = event->pos().x(); - } - - if (ui_->customPlot->axisRect()->rect().contains(event->pos()) || volumeAxisRect_->rect().contains(event->pos())) { - dragStartRangeX_ = ui_->customPlot->xAxis->range(); - dragStartRangeY_ = ui_->customPlot->yAxis2->range(); - dragStartPos_ = event->pos(); - isDraggingMainPlot_ = true; - } - - if (isDraggingXAxis_ || isDraggingYAxis_) { - lastDragCoord_ = event->pos(); - isDraggingMainPlot_ = false; - } -} - -void ChartWidget::OnMouseReleased(QMouseEvent* event) -{ - isDraggingYAxis_ = false; - isDraggingXAxis_ = false; - isDraggingMainPlot_ = false; - //ui_->customPlot->setInteraction(QCP::iRangeDrag, true); -} - -void ChartWidget::OnWheelScroll(QWheelEvent* event) -{ - auto bottomAxis = volumeAxisRect_->axis(QCPAxis::atBottom); - auto lower_bound = volumeAxisRect_->axis(QCPAxis::atBottom)->range().lower; - auto upper_bound = volumeAxisRect_->axis(QCPAxis::atBottom)->range().upper; - auto diff = upper_bound - lower_bound; - auto directionCoeff = event->angleDelta().y() < 0 ? -1 : 1; - double tempCoeff = 120.0 / qAbs(event->angleDelta().y()) * 10; - //change this to impact on xAxis scale speed, the lower coeff the faster scaling - lower_bound += diff / tempCoeff * directionCoeff; - auto lower_limit = candlesticksChart_->data()->constBegin()->key - (upper_bound - lower_bound) * 0.2 ; - if (lower_bound < lower_limit && directionCoeff == -1) { - return; - } - bottomAxis->setRange(lower_bound, upper_bound); - ui_->customPlot->replot(); -} - -void ChartWidget::OnAutoScaleBtnClick() -{ - autoScaling_ = !autoScaling_; - if (autoScaling_) { - rescalePlot(); - } - setAutoScaleBtnColor(); -} - -void ChartWidget::OnResetBtnClick() -{ - if (candlesticksChart_->data()->size()) { - auto new_upper = candlesticksChart_->data()->at(candlesticksChart_->data()->size() - 1)->key + IntervalWidth( - dateRange_.checkedId()) / 1000 / 2; - QCPRange defaultRange(new_upper - IntervalWidth(dateRange_.checkedId(), requestLimit) / 1000, new_upper); - volumeAxisRect_->axis(QCPAxis::atBottom)->setRange(defaultRange); - volumeAxisRect_->axis(QCPAxis::atBottom)->setRange(defaultRange.lower - CountOffsetFromRightBorder(), - defaultRange.upper + CountOffsetFromRightBorder()); - } - if (!autoScaling_) { - autoScaling_ = true; - rescalePlot(); - setAutoScaleBtnColor(); - } -} - -void ChartWidget::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - QMetaObject::invokeMethod(this, &ChartWidget::UpdatePrintFlag, Qt::QueuedConnection);//UpdatePrintFlag should be called after chart have resized, so we put this method to event loop's queue -} - -quint64 ChartWidget::GetCandleTimestamp(const uint64_t& timestamp, const Interval& interval) const -{ - QDateTime now = QDateTime::fromMSecsSinceEpoch(timestamp).toUTC(); - QDateTime result = now; - switch (interval) { - case Interval::OneYear: - { - result.setTime(QTime(0, 0)); - result.setDate(QDate(now.date().year(), 1, 1)); - break; - } - case Interval::SixMonths: - { - int month = now.date().month(); // 1 - January, 12 - December - int mod = month % 6; - result.setTime(QTime(0, 0)); - result.setDate(QDate(now.date().year(), month - mod + 1, 1)); - break; - } - case Interval::OneMonth: - { - result.setTime(QTime(0, 0)); - result.setDate(QDate(now.date().year(), now.date().month(), 1)); - break; - } - case Interval::OneWeek: - { - auto date = now.date(); - auto start = date.addDays(1 - date.dayOfWeek()); //1 - Monday, 7 - Sunday - result.setTime(QTime(0, 0)); - result.setDate(start); - break; - } - case Interval::TwentyFourHours: - result.setTime(QTime(0, 0)); - break; - case Interval::TwelveHours: - { - int hour = now.time().hour(); - int mod = hour % 12; - result.setTime(QTime(hour - mod, 0)); - break; - } - case Interval::SixHours: - { - int hour = now.time().hour(); - int mod = hour % 6; - result.setTime(QTime(hour - mod, 0)); - break; - } - case Interval::OneHour: - result.setTime(QTime(now.time().hour(), 0)); - break; - default: - break; - } - return result.toMSecsSinceEpoch(); -} - -bool ChartWidget::isBeyondUpperLimit(QCPRange newRange, int interval) -{ - return (newRange.size() > IntervalWidth(interval, candleCountOnScreenLimit) / 1000) && !isDraggingMainPlot_; -} - -bool ChartWidget::isBeyondLowerLimit(QCPRange newRange, int interval) -{ - return newRange.size() < IntervalWidth(interval, candleViewLimit) / 1000; -} - -void ChartWidget::OnVolumeAxisRangeChanged(QCPRange newRange, QCPRange oldRange) -{ - auto interval = dateRange_.checkedId() == -1 ? 0 : dateRange_.checkedId(); - - if (isBeyondUpperLimit(newRange, interval) && oldRange.lower >= 0) { - volumeAxisRect_->axis(QCPAxis::atBottom)->setRange( - oldRange.upper - IntervalWidth(interval, candleCountOnScreenLimit) / 1000, oldRange.upper); - ui_->customPlot->xAxis->setRange(volumeAxisRect_->axis(QCPAxis::atBottom)->range()); - } - else { - if (isBeyondLowerLimit(newRange, interval)) { - volumeAxisRect_->axis(QCPAxis::atBottom)->setRange( - oldRange.upper - IntervalWidth(interval, candleViewLimit) / 1000 - 1.0, oldRange.upper); - ui_->customPlot->xAxis->setRange(volumeAxisRect_->axis(QCPAxis::atBottom)->range()); - } - else { - ui_->customPlot->xAxis->setRange(newRange); - } - } - if (!std::isinf(ui_->customPlot->xAxis->range().size() / (IntervalWidth(dateRange_.checkedId()) / 1000))) { - appSettings_->set(ApplicationSettings::ChartCandleCount, int(ui_->customPlot->xAxis->range().size() / (IntervalWidth(dateRange_.checkedId()) / 1000))); - } - LoadAdditionalPoints(newRange); - pickTicketDateFormat(newRange); - rescalePlot(); -} - -QString ChartWidget::ProductTypeToString(TradeHistoryTradeType type) -{ - switch (type) { - case FXTradeType: return QStringLiteral("FX"); - case XBTTradeType: return QStringLiteral("XBT"); - case PMTradeType: return QStringLiteral("PM"); - default: return QStringLiteral(""); - } -} - -void ChartWidget::SetupCrossfire() -{ - QPen pen(Qt::white, 1, Qt::PenStyle::DashLine); - QVector dashes; - qreal space = 8; - dashes << 4 << space; - pen.setDashPattern(dashes); - - vertLine->setLayer(QStringLiteral("axes")); - horLine->setLayer(QStringLiteral("axes")); - - vertLine->start->setType(QCPItemPosition::ptAbsolute); - vertLine->end->setType(QCPItemPosition::ptAbsolute); - vertLine->setPen(pen); - vertLine->setClipToAxisRect(false); - - horLine->start->setType(QCPItemPosition::ptAbsolute); - horLine->end->setType(QCPItemPosition::ptAbsolute); - horLine->setPen(pen); - horLine->setClipToAxisRect(false); - - if (!ui_->customPlot->rect().contains(mapFromGlobal(QCursor::pos()))) { - vertLine->setVisible(false); - horLine->setVisible(false); - } -} - -void ChartWidget::SetupLastPrintFlag() -{ - lastPrintFlag_ = new QCPItemText(ui_->customPlot); - lastPrintFlag_->setVisible(false); - lastPrintFlag_->setPen(Qt::NoPen); - lastPrintFlag_->setColor(Qt::white); - lastPrintFlag_->setBrush(QBrush(c_greenColor)); - auto font = ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->labelFont(); - lastPrintFlag_->setFont(font); - lastPrintFlag_->position->setType(QCPItemPosition::ptAbsolute); - lastPrintFlag_->position->setAxisRect(ui_->customPlot->yAxis2->axisRect()); - lastPrintFlag_->setPositionAlignment(Qt::AlignLeft | Qt::AlignVCenter); - lastPrintFlag_->setLayer(QStringLiteral("axes")); - lastPrintFlag_->setClipAxisRect(ui_->customPlot->yAxis2->axisRect()); - lastPrintFlag_->setClipToAxisRect(false); -} - -void ChartWidget::InitializeCustomPlot() -{ - SetupCrossfire(); - - QBrush bgBrush(BACKGROUND_COLOR); - ui_->customPlot->setBackground(bgBrush); - - ui_->ohlcLbl->setFont(QFont(QStringLiteral("sans"), 10)); - - // create candlestick chart: - candlesticksChart_ = new QCPFinancial(ui_->customPlot->xAxis, ui_->customPlot->yAxis2); - candlesticksChart_->setName(tr("Candlestick")); - candlesticksChart_->setChartStyle(QCPFinancial::csCandlestick); - candlesticksChart_->setTwoColored(true); - candlesticksChart_->setBrushPositive(c_greenColor); - candlesticksChart_->setBrushNegative(c_redColor); - candlesticksChart_->setPenPositive(QPen(c_greenColor)); - candlesticksChart_->setPenNegative(QPen(c_redColor)); - - ui_->customPlot->axisRect()->axis(QCPAxis::atLeft)->setVisible(false); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setVisible(true); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setBasePen(QPen(FOREGROUND_COLOR)); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setTickPen(QPen(FOREGROUND_COLOR)); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setSubTickPen(QPen(FOREGROUND_COLOR)); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setTickLabelColor(FOREGROUND_COLOR); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setTickLength(0, 8); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setSubTickLength(0, 4); - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->setNumberFormat(QStringLiteral("f")); - ui_->customPlot->axisRect()->axis(QCPAxis::atBottom)->grid()->setPen(Qt::NoPen); - - // create bottom axis rect for volume bar chart: - volumeAxisRect_ = new QCPAxisRect(ui_->customPlot); - ui_->customPlot->plotLayout()->addElement(1, 0, volumeAxisRect_); - volumeAxisRect_->setMaximumSize(QSize(QWIDGETSIZE_MAX, 100)); - volumeAxisRect_->axis(QCPAxis::atBottom)->setLayer(QStringLiteral("axes")); - volumeAxisRect_->axis(QCPAxis::atBottom)->grid()->setLayer(QStringLiteral("grid")); - // bring bottom and main axis rect closer together: - ui_->customPlot->plotLayout()->setRowSpacing(0); - volumeAxisRect_->setAutoMargins(QCP::msLeft | QCP::msRight | QCP::msBottom); - volumeAxisRect_->setMargins(QMargins(0, 0, 0, 0)); - // create two bar plottables, for positive (green) and negative (red) volume bars: - ui_->customPlot->setAutoAddPlottableToLegend(false); - - volumeChart_ = new QCPBars(volumeAxisRect_->axis(QCPAxis::atBottom), volumeAxisRect_->axis(QCPAxis::atRight)); - volumeChart_->setPen(QPen(VOLUME_COLOR)); - volumeChart_->setBrush(VOLUME_COLOR); - - volumeAxisRect_->axis(QCPAxis::atLeft)->setVisible(false); - volumeAxisRect_->axis(QCPAxis::atRight)->setVisible(true); - volumeAxisRect_->axis(QCPAxis::atRight)->setBasePen(QPen(FOREGROUND_COLOR)); - volumeAxisRect_->axis(QCPAxis::atRight)->setTickPen(QPen(FOREGROUND_COLOR)); - volumeAxisRect_->axis(QCPAxis::atRight)->setSubTickPen(QPen(FOREGROUND_COLOR)); - volumeAxisRect_->axis(QCPAxis::atRight)->setTickLabelColor(FOREGROUND_COLOR); - volumeAxisRect_->axis(QCPAxis::atRight)->setTickLength(0, 8); - volumeAxisRect_->axis(QCPAxis::atRight)->setSubTickLength(0, 4); - volumeAxisRect_->axis(QCPAxis::atRight)->ticker()->setTickCount(2); - volumeAxisRect_->axis(QCPAxis::atRight)->setTickLabelFont( - ui_->customPlot->axisRect()->axis(QCPAxis::atRight)->labelFont()); - - volumeAxisRect_->axis(QCPAxis::atBottom)->setBasePen(QPen(FOREGROUND_COLOR)); - volumeAxisRect_->axis(QCPAxis::atBottom)->setTickPen(QPen(FOREGROUND_COLOR)); - volumeAxisRect_->axis(QCPAxis::atBottom)->setSubTickPen(QPen(FOREGROUND_COLOR)); - volumeAxisRect_->axis(QCPAxis::atBottom)->setTickLength(0, 8); - volumeAxisRect_->axis(QCPAxis::atBottom)->setSubTickLength(0, 4); - volumeAxisRect_->axis(QCPAxis::atBottom)->setTickLabelColor(FOREGROUND_COLOR); - volumeAxisRect_->axis(QCPAxis::atBottom)->grid()->setPen(Qt::NoPen); - - // interconnect x axis ranges of main and bottom axis rects: - connect(ui_->customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)) - , volumeAxisRect_->axis(QCPAxis::atBottom), SLOT(setRange(QCPRange))); - - connect(volumeAxisRect_->axis(QCPAxis::atBottom), - qOverload(&QCPAxis::rangeChanged), - this, - &ChartWidget::OnVolumeAxisRangeChanged); - - SetupLastPrintFlag(); - - connect(ui_->customPlot->yAxis2, - qOverload(&QCPAxis::rangeChanged), - this, - [this]() {UpdatePrintFlag(); }); - - // configure axes of both main and bottom axis rect: - dateTimeTicker->setDateTimeSpec(Qt::UTC); - dateTimeTicker->setDateTimeFormat(QStringLiteral("dd/MM/yy\nHH:mm")); - dateTimeTicker->setTickCount(17); - volumeAxisRect_->axis(QCPAxis::atBottom)->setTicker(dateTimeTicker); - //volumeAxisRect_->axis(QCPAxis::atBottom)->setTickLabelRotation(10); - volumeAxisRect_->axis(QCPAxis::atBottom)->setTickLabelFont(QFont(QStringLiteral("Arial"), 9)); - ui_->customPlot->xAxis->setBasePen(Qt::NoPen); - ui_->customPlot->xAxis->setTickLabels(false); - ui_->customPlot->xAxis->setTicks(false); - // only want vertical grid in main axis rect, so hide xAxis backbone, ticks, and labels - ui_->customPlot->xAxis->setTicker(dateTimeTicker); - ui_->customPlot->rescaleAxes(); - ui_->customPlot->xAxis->scaleRange(1.025, ui_->customPlot->xAxis->range().center()); - ui_->customPlot->yAxis->scaleRange(1.1, ui_->customPlot->yAxis->range().center()); - - // make axis rects' left side line up: - QCPMarginGroup* group = new QCPMarginGroup(ui_->customPlot); - ui_->customPlot->axisRect()->setMarginGroup(QCP::msLeft | QCP::msRight, group); - volumeAxisRect_->setMarginGroup(QCP::msLeft | QCP::msRight, group); - - connect(ui_->customPlot, &QCustomPlot::mouseMove, this, &ChartWidget::OnPlotMouseMove); - connect(ui_->customPlot, &QCustomPlot::mousePress, this, &ChartWidget::OnMousePressed); - connect(ui_->customPlot, &QCustomPlot::mouseRelease, this, &ChartWidget::OnMouseReleased); - connect(ui_->customPlot, &QCustomPlot::mouseWheel, this, &ChartWidget::OnWheelScroll); - volumeAxisRect_->axis(QCPAxis::atRight)->setRange(0, 1000); -} - -void ChartWidget::OnLoadingNetworkSettings() -{ - ui_->pushButtonMDConnection->setText(tr("Connecting")); - ui_->pushButtonMDConnection->setEnabled(false); - ui_->pushButtonMDConnection->setToolTip(tr("Waiting for connection details")); -} - -void ChartWidget::OnMDConnecting() -{ - ui_->pushButtonMDConnection->setText(tr("Connecting")); - ui_->pushButtonMDConnection->setEnabled(false); - ui_->pushButtonMDConnection->setToolTip(QString{}); -} - -void ChartWidget::OnMDConnected() -{ - ui_->pushButtonMDConnection->setText(tr("Disconnect")); - ui_->pushButtonMDConnection->setEnabled(!authorized_); -} - -void ChartWidget::OnMDDisconnecting() -{ - ui_->pushButtonMDConnection->setText(tr("Disconnecting")); - ui_->pushButtonMDConnection->setEnabled(false); - - if (candlesticksChart_ != nullptr) - candlesticksChart_->data()->clear(); - - if (volumeChart_ != nullptr) - volumeChart_->data()->clear(); - - ui_->ohlcLbl->setText({}); - ui_->customPlot->replot(); - - mdProvider_->UnsubscribeFromMD(); - mdProvider_->DisconnectFromMDSource(); -} - -void ChartWidget::OnMDDisconnected() -{ - ui_->pushButtonMDConnection->setText(tr("Subscribe")); - ui_->pushButtonMDConnection->setEnabled(!authorized_); -} - -void ChartWidget::ChangeMDSubscriptionState() -{ - if (mdProvider_->IsConnectionActive()) { - mdProvider_->DisconnectFromMDSource(); - } - else { - mdProvider_->SubscribeToMD(); - } -} - -void ChartWidget::OnNewTrade(const std::string& productName, uint64_t timestamp, double price, double amount) -{ - if (productName != getCurrentProductName().toStdString() || - !candlesticksChart_->data()->size() || - !volumeChart_->data()->size()) { - return; - } - - auto lastVolume = volumeChart_->data()->end() - 1; - lastVolume->value += amount; - auto lastCandle = candlesticksChart_->data()->end() - 1; - lastCandle->high = qMax(lastCandle->high, price); - lastCandle->low = qMin(lastCandle->low, price); - if (!qFuzzyCompare(lastCandle->close, price) || !qFuzzyIsNull(amount)) { - isHigh_ = price > lastClose_; - lastClose_ = price; - UpdatePrintFlag(); - lastCandle->close = price; - UpdateOHLCInfo(IntervalWidth(dateRange_.checkedId()) / 1000, - ui_->customPlot->xAxis->pixelToCoord(ui_->customPlot->mapFromGlobal(QCursor::pos()).x())); - rescalePlot(); - ui_->customPlot->replot(); - } - CheckToAddNewCandle(timestamp); -} - -void ChartWidget::OnNewXBTorFXTrade(const bs::network::NewTrade& trade) -{ - OnNewTrade(trade.product, trade.timestamp, trade.price, trade.amount); -} - -void ChartWidget::OnNewPMTrade(const bs::network::NewPMTrade& trade) -{ - OnNewTrade(trade.product, trade.timestamp, trade.price, trade.amount); -} diff --git a/BlockSettleUILib/ChartWidget.h b/BlockSettleUILib/ChartWidget.h deleted file mode 100644 index ffd1f689b..000000000 --- a/BlockSettleUILib/ChartWidget.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHARTWIDGET_H -#define CHARTWIDGET_H - -#include -#include -#include "CommonTypes.h" -#include "CustomControls/qcustomplot.h" -#include "market_data_history.pb.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class ChartWidget; } -class QStandardItemModel; -class QCPTextElement; -class QCPFinancial; -class QCPBars; -class QCPAxisRect; -QT_END_NAMESPACE - -class ApplicationSettings; -class MarketDataProvider; -class MDCallbacksQt; -class ConnectionManager; -namespace spdlog { class logger; } -class MdhsClient; - -#include -#include - -class ComboBoxDelegate : public QItemDelegate -{ - Q_OBJECT -public: - explicit ComboBoxDelegate(QObject *parent = nullptr); -protected: - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; -}; - -class ChartWidget : public QWidget -{ - Q_OBJECT - -public: - explicit ChartWidget(QWidget* pParent = nullptr); - ~ChartWidget() override; - void SendEoDRequest(); - void init(const std::shared_ptr& - , const std::shared_ptr& - , const std::shared_ptr & - , const std::shared_ptr& - , const std::shared_ptr&); - - void setAuthorized(bool authorized); - void disconnect(); - -protected slots: - void OnDataReceived(const std::string& data); - void OnDateRangeChanged(int id); - void OnMdUpdated(bs::network::Asset::Type, const QString &security, bs::network::MDFields); - void OnInstrumentChanged(const QString &text); - QString GetFormattedStamp(double timestamp); - void UpdateOHLCInfo(double width, double timestamp); - void OnPlotMouseMove(QMouseEvent* event); - void leaveEvent(QEvent* event) override; - void rescaleCandlesYAxis(); - void rescaleVolumesYAxis() const; - void rescalePlot(); - void OnMousePressed(QMouseEvent* event); - void OnMouseReleased(QMouseEvent* event); - void OnWheelScroll(QWheelEvent* event); - void OnAutoScaleBtnClick(); - void OnResetBtnClick(); - void resizeEvent(QResizeEvent* event) override; - bool isBeyondUpperLimit(QCPRange newRange, int interval); - bool isBeyondLowerLimit(QCPRange newRange, int interval); - void OnVolumeAxisRangeChanged(QCPRange newRange, QCPRange oldRange); - static QString ProductTypeToString(Blocksettle::Communication::TradeHistory::TradeHistoryTradeType type); - void SetupCrossfire(); - void SetupLastPrintFlag(); - - void OnLoadingNetworkSettings(); - void OnMDConnecting(); - void OnMDConnected(); - void OnMDDisconnecting(); - void OnMDDisconnected(); - void ChangeMDSubscriptionState(); - - void OnNewTrade(const std::string& productName, uint64_t timestamp, double price, double amount); - void OnNewXBTorFXTrade(const bs::network::NewTrade& trade); - void OnNewPMTrade(const bs::network::NewPMTrade& trade); - -protected: - quint64 GetCandleTimestamp(const uint64_t& timestamp, - const Blocksettle::Communication::MarketDataHistory::Interval& interval) const; - void AddDataPoint(const qreal& open, const qreal& high, const qreal& low, const qreal& close, const qreal& timestamp, const qreal& volume) const; - void UpdateChart(const int& interval); - void InitializeCustomPlot(); - quint64 IntervalWidth(int interval = -1, int count = 1, const QDateTime& specialDate = {}) const; - static int FractionSizeForProduct(Blocksettle::Communication::TradeHistory::TradeHistoryTradeType type); - void ProcessProductsListResponse(const std::string& data); - void ProcessOhlcHistoryResponse(const std::string& data); - void ProcessEodResponse(const std::string& data); - double CountOffsetFromRightBorder(); - - void CheckToAddNewCandle(qint64 stamp); - - void setAutoScaleBtnColor() const; - - void DrawCrossfire(QMouseEvent* event); - - void UpdatePrintFlag(); - - void UpdatePlot(const int& interval, const qint64& timestamp); - - bool needLoadNewData(const QCPRange& range, QSharedPointer data) const; - - void LoadAdditionalPoints(const QCPRange& range); - - void pickTicketDateFormat(const QCPRange& range) const; -private: - QString getCurrentProductName() const; - void AddParentItem(QStandardItemModel * model, const QString& text); - void AddChildItem(QStandardItemModel* model, const QString& text); - -private: - std::shared_ptr appSettings_; - std::shared_ptr mdProvider_; - std::shared_ptr mdhsClient_; - std::shared_ptr logger_; - - bool isProductListInitialized_{ false }; - std::map productTypesMapper; - - QSharedPointer dateTimeTicker{ new QCPAxisTickerDateTime }; - - const int loadDistance{ 15 }; - - bool eodUpdated_{ false }; - bool eodRequestSent_{ false }; - - constexpr static int requestLimit{ 200 }; - constexpr static int candleViewLimit{ 150 }; - constexpr static qint64 candleCountOnScreenLimit{ 1500 }; - - Blocksettle::Communication::MarketDataHistory::OhlcCandle lastCandle_; - - double prevRequestStamp{ 0.0 }; - - double zoomDiff_{ 0.0 }; - - Ui::ChartWidget *ui_; - QButtonGroup dateRange_; - QStandardItemModel *cboModel_; - QCPFinancial *candlesticksChart_; - QCPBars *volumeChart_; - QCPAxisRect *volumeAxisRect_; - - QCPItemText * lastPrintFlag_{ nullptr }; - bool isHigh_ { true }; - - QCPItemLine* horLine; - QCPItemLine* vertLine; - - double lastHigh_; - double lastLow_; - double lastClose_; - double currentTimestamp_; - quint64 newestCandleTimestamp_{}; - - bool autoScaling_{ true }; - - qreal currentMinPrice_{ 0 }; - qreal currentMaxPrice_{ 0 }; - - int lastInterval_; - int dragY_; - - bool isDraggingYAxis_; - bool isDraggingXAxis_{ false }; - bool isDraggingMainPlot_{ false }; - QCPRange dragStartRangeX_; - QCPRange dragStartRangeY_; - QPointF dragStartPos_; - - QPoint lastDragCoord_; - qreal startDragCoordX_{ 0.0 }; - - quint64 firstTimestampInDb_{ 0 }; - bool authorized_{ false }; - - std::set pmProducts_; -}; - -#endif // CHARTWIDGET_H diff --git a/BlockSettleUILib/ChartWidget.ui b/BlockSettleUILib/ChartWidget.ui deleted file mode 100644 index 1f6370f9a..000000000 --- a/BlockSettleUILib/ChartWidget.ui +++ /dev/null @@ -1,291 +0,0 @@ - - - - ChartWidget - - - - 0 - 0 - 1137 - 711 - - - - Form - - - - 0 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - false - - - true - - - - 0 - - - 4 - - - 4 - - - 4 - - - 0 - - - - - - 120 - 0 - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Subscribe - - - - - - - auto scaling - - - - - - - Reset - - - - - - - - 40 - 16777215 - - - - 1y - - - true - - - - - - - - 40 - 16777215 - - - - 6m - - - true - - - - - - - - 40 - 16777215 - - - - 1m - - - true - - - - - - - - 40 - 16777215 - - - - 1w - - - true - - - - - - - - 40 - 16777215 - - - - 24h - - - true - - - - - - - - 40 - 16777215 - - - - 12h - - - true - - - - - - - - 40 - 16777215 - - - - 6h - - - true - - - - - - - - 40 - 16777215 - - - - 1h - - - true - - - - - - - - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - - - - - - - - - QCustomPlot - QWidget -
CustomControls/qcustomplot.h
- 1 -
-
- - -
diff --git a/BlockSettleUILib/ChatUI/BSChatInput.cpp b/BlockSettleUILib/ChatUI/BSChatInput.cpp deleted file mode 100644 index d37cf2a7d..000000000 --- a/BlockSettleUILib/ChatUI/BSChatInput.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "BSChatInput.h" - -#include - -BSChatInput::BSChatInput(QWidget *parent) - : QTextBrowser(parent) -{ - -} -BSChatInput::BSChatInput(const QString &text, QWidget *parent) - : QTextBrowser(parent) -{ - setText(text); -} - -BSChatInput::~BSChatInput() = default; - -void BSChatInput::keyPressEvent(QKeyEvent * e) -{ - //Qt::Key_Return - Main Enter key - //Qt::Key_Enter = Numpad Enter key - if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { - if (e->modifiers().testFlag(Qt::ShiftModifier)) { - this->insertPlainText(QStringLiteral("\n")); - - } else { - emit sendMessage(); - } - return e->ignore(); - } - else if (e->modifiers().testFlag(Qt::ControlModifier)) { - if (Qt::Key_C == e->key()) { - // If there no selection than could be that we going to copy text from other element - // which cannot have focus. - if (!textCursor().hasSelection()) { - e->setAccepted(false); - return; - } - } - else if (Qt::Key_V == e->key()) { - QTextBrowser::keyPressEvent(e); - auto cursor = textCursor(); - cursor.setCharFormat({}); - setTextCursor(cursor); - return; - } - } - - return QTextBrowser::keyPressEvent(e); -} diff --git a/BlockSettleUILib/ChatUI/BSChatInput.h b/BlockSettleUILib/ChatUI/BSChatInput.h deleted file mode 100644 index d5222c6f6..000000000 --- a/BlockSettleUILib/ChatUI/BSChatInput.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef BSCHATINPUT_H -#define BSCHATINPUT_H - -#include - -class BSChatInput : public QTextBrowser { - Q_OBJECT -public: - BSChatInput(QWidget *parent = nullptr); - BSChatInput(const QString &text, QWidget *parent = nullptr); - ~BSChatInput() override; - -signals: - void sendMessage(); - -public: - void keyPressEvent(QKeyEvent * e) override; -}; - -#endif // BSCHATINPUT_H diff --git a/BlockSettleUILib/ChatUI/ChatClientUsersViewItemDelegate.cpp b/BlockSettleUILib/ChatUI/ChatClientUsersViewItemDelegate.cpp deleted file mode 100644 index dc1da292c..000000000 --- a/BlockSettleUILib/ChatUI/ChatClientUsersViewItemDelegate.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatClientUsersViewItemDelegate.h" -#include -#include - -namespace { - const int kDotSize = 8; - const QString kDotPathname = QLatin1String{ ":/ICON_DOT" }; -} - -using namespace bs; - -ChatClientUsersViewItemDelegate::ChatClientUsersViewItemDelegate(ChatPartiesSortProxyModelPtr proxyModel, QObject *parent) - : QStyledItemDelegate (parent) - , proxyModel_(proxyModel) -{ -} - -void ChatClientUsersViewItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - if (!index.isValid()) { - return; - } - - const QModelIndex& sourceIndex = proxyModel_ ? proxyModel_->mapToSource(index) : index; - if (!sourceIndex.isValid()) { - return; - } - - PartyTreeItem* internalData = static_cast(sourceIndex.internalPointer()); - if (internalData->modelType() == UI::ElementType::Container) { - paintPartyContainer(painter, option, sourceIndex); - } - else if (internalData->modelType() == UI::ElementType::Party) { - paintParty(painter, option, sourceIndex); - } -} - -void ChatClientUsersViewItemDelegate::paintPartyContainer(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QStyleOptionViewItem itemOption(option); - - if (itemOption.state & QStyle::State_Selected) { - painter->save(); - painter->fillRect(itemOption.rect, itemStyle_.colorHighlightBackground()); - painter->restore(); - } - - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorCategoryItem()); - PartyTreeItem* internalData = static_cast(index.internalPointer()); - Q_ASSERT(internalData && internalData->data().canConvert()); - itemOption.text = internalData->data().toString(); - - QStyledItemDelegate::paint(painter, itemOption, index); -} - -void ChatClientUsersViewItemDelegate::paintParty(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QStyleOptionViewItem itemOption(option); - if (itemOption.state & QStyle::State_Selected) { - painter->save(); - painter->fillRect(itemOption.rect, itemStyle_.colorHighlightBackground()); - painter->restore(); - } - - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorRoom()); - PartyTreeItem* party = static_cast(index.internalPointer()); - Chat::ClientPartyPtr clientPartyPtr = party->data().value(); - if (!clientPartyPtr) { - return; - } - - itemOption.text = QString::fromStdString(clientPartyPtr->displayName()); - if (clientPartyPtr->isPrivateStandard() || clientPartyPtr->isPrivateOTC()) { - if (Chat::PartyState::INITIALIZED == clientPartyPtr->partyState()) { - paintInitParty(party, painter, itemOption); - } - else { - paintRequestParty(clientPartyPtr, painter, itemOption); - } - } - - QStyledItemDelegate::paint(painter, itemOption, index); - - if (party->hasNewMessages() && Chat::PartyState::REQUESTED != clientPartyPtr->partyState()) { - painter->save(); - QFontMetrics fm(itemOption.font, painter->device()); - auto textRect = fm.boundingRect(itemOption.rect, 0, itemOption.text); - const QPixmap pixmap(kDotPathname); - const QRect r(itemOption.rect.left() + textRect.width() + kDotSize, - itemOption.rect.top() + itemOption.rect.height() / 2 - kDotSize / 2 + 1, - kDotSize, kDotSize); - painter->drawPixmap(r, pixmap, pixmap.rect()); - painter->restore(); - } -} - -void ChatClientUsersViewItemDelegate::paintInitParty(PartyTreeItem* partyTreeItem, QPainter* painter, - QStyleOptionViewItem& itemOption) const -{ - Chat::ClientPartyPtr clientPartyPtr = partyTreeItem->data().value(); - // This should be always true as far as we checked it in previous flow function - assert(clientPartyPtr); - - switch (clientPartyPtr->clientStatus()) - { - case Chat::ClientStatus::ONLINE: - { - QColor palleteColor = itemStyle_.colorContactOnline(); - if (partyTreeItem->isOTCTogglingMode() && !partyTreeItem->activeOTCToggleState()) { - palleteColor = itemStyle_.colorContactOffline(); - } - - itemOption.palette.setColor(QPalette::Text, palleteColor); - } - break; - - case Chat::ClientStatus::OFFLINE: - { - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorContactOffline()); - if (itemOption.state & QStyle::State_Selected) { - painter->save(); - painter->fillRect(itemOption.rect, itemStyle_.colorContactOffline()); - painter->restore(); - } - } - break; - - default: - { - // You should specify rules for new ClientStatus explicitly - Q_ASSERT(false); - } - break; - } -} - -void ChatClientUsersViewItemDelegate::paintRequestParty(Chat::ClientPartyPtr& clientPartyPtr, QPainter* painter, - QStyleOptionViewItem& itemOption) const -{ - switch (clientPartyPtr->partyState()) { - case Chat::PartyState::UNINITIALIZED: - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorContactOutgoing()); - break; - case Chat::PartyState::REQUESTED: - if (clientPartyPtr->partyCreatorHash() == proxyModel_->currentUser()) { - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorContactOutgoing()); - } - else { - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorContactIncoming()); - } - break; - default: - break; - } -} - -QWidget *ChatClientUsersViewItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QWidget * editor = QStyledItemDelegate::createEditor(parent, option, index); - editor->setProperty("contact_editor", true); - return editor; -} diff --git a/BlockSettleUILib/ChatUI/ChatClientUsersViewItemDelegate.h b/BlockSettleUILib/ChatUI/ChatClientUsersViewItemDelegate.h deleted file mode 100644 index 6eec056e7..000000000 --- a/BlockSettleUILib/ChatUI/ChatClientUsersViewItemDelegate.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATCLIENTUSERSVIEWITEMDELEGATE_H -#define CHATCLIENTUSERSVIEWITEMDELEGATE_H - -#include -#include "ChatUsersViewItemStyle.h" - -#include "ChatPartiesSortProxyModel.h" - -class ChatClientUsersViewItemDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - explicit ChatClientUsersViewItemDelegate(ChatPartiesSortProxyModelPtr proxyModel, QObject *parent = nullptr); - -public: - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - -protected: - void paintPartyContainer(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - void paintParty(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - - void paintInitParty(PartyTreeItem* partyTreeItem, QPainter* painter, - QStyleOptionViewItem& itemOption) const; - void paintRequestParty(Chat::ClientPartyPtr& clientPartyPtr, QPainter* painter, - QStyleOptionViewItem& itemOption) const; - -private: - ChatUsersViewItemStyle itemStyle_; - ChatPartiesSortProxyModelPtr proxyModel_; -}; -#endif // CHATCLIENTUSERSVIEWITEMDELEGATE_H diff --git a/BlockSettleUILib/ChatUI/ChatMessagesTextEdit.cpp b/BlockSettleUILib/ChatUI/ChatMessagesTextEdit.cpp deleted file mode 100644 index 96744f902..000000000 --- a/BlockSettleUILib/ChatUI/ChatMessagesTextEdit.cpp +++ /dev/null @@ -1,660 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatMessagesTextEdit.h" - -#include "OtcUtils.h" -#include "RequestPartyBox.h" -#include "chat.pb.h" - -#include -#include -#include -#include -#include - -#include - -namespace { - // Translation - const QString ownSenderUserName = QObject::tr("you"); - const QString contextMenuCopy = QObject::tr("Copy"); - const QString contextMenuCopyLink = QObject::tr("Copy Link Location"); - const QString contextMenuSelectAll= QObject::tr("Select All"); - const QString contextMenuAddUserMenu = QObject::tr("Add to contacts"); - const QString contextMenuAddUserMenuStatusTip = QObject::tr("Click to add user to contact list"); - const QString contextMenuRemoveUserMenu = QObject::tr("Remove from contacts"); - const QString contextMenuRemoveUserMenuStatusTip = QObject::tr("Click to remove user from contact list"); -} - -ChatMessagesTextEdit::ChatMessagesTextEdit(QWidget* parent) - : QTextBrowser(parent) - , internalStyle_(this) -{ - tableFormat_.setBorder(0); - tableFormat_.setCellPadding(0); - tableFormat_.setCellSpacing(0); - - setupHighlightPalette(); - - connect(this, &QTextBrowser::anchorClicked, this, &ChatMessagesTextEdit::onUrlActivated); - connect(this, &QTextBrowser::textChanged, this, &ChatMessagesTextEdit::onTextChanged); -} - -void ChatMessagesTextEdit::setupHighlightPalette() -{ - auto highlightPalette = palette(); - highlightPalette.setColor(QPalette::Inactive, QPalette::Highlight, highlightPalette.color(QPalette::Active, QPalette::Highlight)); - highlightPalette.setColor(QPalette::Inactive, QPalette::HighlightedText, highlightPalette.color(QPalette::Active, QPalette::HighlightedText)); - setPalette(highlightPalette); -} - -int ChatMessagesTextEdit::messagesCount(const std::string& partyId) const -{ - return messages_[partyId].count(); -} - -Chat::MessagePtr ChatMessagesTextEdit::getMessage(const std::string& partyId, const std::string& messageId) const -{ - const auto it = std::find_if(messages_[partyId].begin(), messages_[partyId].end(), [messageId](const Chat::MessagePtr& msg)->bool { - return msg->messageId() == messageId; - }); - - if (it != messages_[partyId].end()) { - return *it; - } - - return nullptr; -} - -QString ChatMessagesTextEdit::data(const std::string& partyId, const std::string& messageId, const Column &column) -{ - if (messages_[partyId].empty()) { - return QString(); - } - - return dataMessage(partyId, messageId, column); -} - -QString ChatMessagesTextEdit::dataMessage(const std::string& partyId, const std::string& messageId, const ChatMessagesTextEdit::Column &column) const -{ - const auto message = getMessage(partyId, messageId); - - if (!message) { - return QString(); - } - - Chat::ClientPartyPtr previousClientPartyPtr = nullptr; - - switch (column) { - case Column::Time: - { - const auto dateTime = message->timestamp().toLocalTime(); - return toHtmlText(dateTime.toString(QString::fromUtf8("MM/dd/yy hh:mm:ss"))); - } - - case Column::User: - { - const auto& senderHash = message->senderHash(); - - if (senderHash == ownUserId_) { - return ownSenderUserName; - } - - if (!previousClientPartyPtr || previousClientPartyPtr->id() != partyId) { - previousClientPartyPtr = partyModel_->getClientPartyById(message->partyId()); - } - - if (!previousClientPartyPtr->isGlobal()) { - return elideUserName(previousClientPartyPtr->displayName()); - } - - const auto clientPartyPtr = partyModel_->getClientPartyById(partyId); - - if (clientPartyPtr && clientPartyPtr->isPrivate()) { - return toHtmlUsername(clientPartyPtr->displayName(), clientPartyPtr->userHash()); - } - return toHtmlUsername(senderHash, senderHash); - } - case Column::Status:{ - return QString(); - } - case Column::Message: { - //QString text = QLatin1String("[%1] %2"); - //text = text.arg(QString::fromStdString(message->messageText())); - - //if (ChatUtils::messageFlagRead(message, Chat::Data_Message_State_INVALID)) { - // return toHtmlInvalid(text.arg(QLatin1String("INVALID MESSAGE!"))); - //} else if (message.encryption() == Chat::Data_Message_Encryption_IES) { - // return toHtmlInvalid(text.arg(QLatin1String("IES ENCRYPTED!"))); - //} else if ( message.encryption() == Chat::Data_Message_Encryption_AEAD) { - // return toHtmlInvalid(text.arg(QLatin1String("AEAD ENCRYPTED!"))); - //} - return toHtmlText(QString::fromStdString(message->messageText())); - } - default: - break; - } - - return QString(); -} - -QImage ChatMessagesTextEdit::statusImage(const std::string& partyId, const std::string& messageId) const -{ - const auto message = getMessage(partyId, messageId); - - if (!message) { - return statusImageGreyUnsent_; - } - - if (message->senderHash() != ownUserId_) { - return QImage(); - } - - auto statusImage = statusImageGreyUnsent_; - - const auto clientPartyPtr = partyModel_->getClientPartyById(message->partyId()); - - if (!clientPartyPtr) { - return QImage(); - } - - if (clientPartyPtr->isGlobalStandard()) { - if ((message->partyMessageState() != Chat::PartyMessageState::UNSENT)) { - statusImage = statusImageBlueSeen_; - } - return statusImage; - } - - if (message->partyMessageState() == Chat::PartyMessageState::UNSENT) { - statusImage = statusImageGreyUnsent_; - } else if (message->partyMessageState() == Chat::PartyMessageState::SENT) { - statusImage = statusImageYellowSent_; - } else if (message->partyMessageState() == Chat::PartyMessageState::RECEIVED) { - statusImage = statusImageGreenReceived_; - } else if (message->partyMessageState() == Chat::PartyMessageState::SEEN) { - statusImage = statusImageBlueSeen_; - } - - return statusImage; -} - -void ChatMessagesTextEdit::contextMenuEvent(QContextMenuEvent *e) -{ - textCursor_ = cursorForPosition(e->pos()); - - // keep selection - if (textCursor().hasSelection()) { - textCursor_.setPosition(textCursor().selectionStart(), QTextCursor::MoveAnchor); - textCursor_.setPosition(textCursor().selectionEnd(), QTextCursor::KeepAnchor); - } - - setTextCursor(textCursor_); - QString text = textCursor_.block().text(); - - //show contact context menu when username is right clicked in User column - if ((textCursor_.block().blockNumber() - 1) % 5 == static_cast(Column::User)) { - if (text != ownSenderUserName) { - const QUrl link = anchorAt(e->pos()); - if (!link.isEmpty()) { - text = link.path(); - } - std::unique_ptr userMenuPtr = initUserContextMenu(text); - userMenuPtr->exec(QCursor::pos()); - return; - } - } - - // show default text context menu - if (text.length() > 0 || textCursor_.hasSelection()) { - QMenu contextMenu(this); - - QAction copyAction(contextMenuCopy, this); - QAction copyLinkLocationAction(contextMenuCopyLink, this); - QAction selectAllAction(contextMenuSelectAll, this); - - connect(©Action, &QAction::triggered, this, &ChatMessagesTextEdit::onCopyActionTriggered); - connect(©LinkLocationAction, &QAction::triggered, this, &ChatMessagesTextEdit::onCopyLinkLocationActionTriggered); - connect(&selectAllAction, &QAction::triggered, this, &ChatMessagesTextEdit::onSelectAllActionTriggered); - - contextMenu.addAction(©Action); - - // show Copy Link Location only when it needed - anchor_ = this->anchorAt(e->pos()); - if (!anchor_.isEmpty()) { - contextMenu.addAction(©LinkLocationAction); - } - - contextMenu.addSeparator(); - contextMenu.addAction(&selectAllAction); - - contextMenu.exec(e->globalPos()); - } -} - -void ChatMessagesTextEdit::onCopyActionTriggered() const -{ - if (textCursor_.hasSelection()) { - QApplication::clipboard()->setText(getFormattedTextFromSelection()); - } - else { - QTextDocument doc; - doc.setHtml(textCursor_.block().text()); - QApplication::clipboard()->setText(doc.toPlainText()); - } -} - -void ChatMessagesTextEdit::onCopyLinkLocationActionTriggered() const -{ - QApplication::clipboard()->setText(anchor_); -} - -void ChatMessagesTextEdit::onSelectAllActionTriggered() -{ - this->selectAll(); -} - -void ChatMessagesTextEdit::onTextChanged() const -{ - verticalScrollBar()->setValue(verticalScrollBar()->maximum()); -} - -void ChatMessagesTextEdit::onUserUrlOpened(const QUrl &url) -{ - const std::string userId = url.path().toStdString(); - const Chat::ClientPartyPtr clientPartyPtr = partyModel_->getStandardPartyForUsers(ownUserId_, userId); - - if (!clientPartyPtr) { - onShowRequestPartyBox(userId); - return; - } - - if (Chat::PartyState::REJECTED == clientPartyPtr->partyState()) { - onShowRequestPartyBox(clientPartyPtr->userHash()); - return; - } - - if (clientPartyPtr->id() == currentPartyId_) { - return; - } - - emit switchPartyRequest(QString::fromStdString(clientPartyPtr->id())); -} - -void ChatMessagesTextEdit::onShowRequestPartyBox(const std::string& userHash) -{ - std::string requestUserHash = userHash; - - const Chat::ClientPartyPtr clientPartyPtr = partyModel_->getStandardPartyForUsers(ownUserId_, requestUserHash); - - const QString requestNote = tr("You can enter initial message below:"); - const QString requestTitle = tr("Do you want to send friend request to %1 ?").arg(QString::fromStdString(requestUserHash)); - - RequestPartyBox rpBox(requestTitle, requestNote); - if (rpBox.exec() == QDialog::Accepted) { - if (clientPartyPtr && Chat::PartyState::REJECTED == clientPartyPtr->partyState()) { - emit removePartyRequest(clientPartyPtr->id()); - requestUserHash = clientPartyPtr->userHash(); - } - - emit newPartyRequest(requestUserHash, rpBox.getCustomMessage().toStdString()); - } -} - -void ChatMessagesTextEdit::onSwitchToChat(const std::string& partyId) -{ - currentPartyId_ = partyId; - clear(); - if (!currentPartyId_.empty()) { - showMessages(partyId); - onTextChanged(); - auto clientMessagesHistory = messages_[partyId]; - - if (clientMessagesHistory.empty()) { - return; - } - - auto riter = clientMessagesHistory.rbegin(); - for (; riter != clientMessagesHistory.rend(); ++riter) - { - const auto messagePtr = (*riter); - - if (messagePtr->partyMessageState() == Chat::PartyMessageState::SEEN) { - continue; - } - - if (messagePtr->senderHash() == ownUserId_) { - continue; - } - - emit messageRead(messagePtr->partyId(), messagePtr->messageId()); - } - } -} - -void ChatMessagesTextEdit::onLogout() -{ - onSwitchToChat({}); -} - -Chat::MessagePtr ChatMessagesTextEdit::onMessageStatusChanged(const std::string& partyId, const std::string& message_id, - const int party_message_state) -{ - Chat::MessagePtr message = findMessage(partyId, message_id); - - if (message) { - message->setPartyMessageState(static_cast(party_message_state)); - notifyMessageChanged(message); - } - - return message; -} - -void ChatMessagesTextEdit::onSetColumnsWidth(int time, int icon, int user, int message) -{ - QVector col_widths; - col_widths << QTextLength(QTextLength::FixedLength, time); - col_widths << QTextLength(QTextLength::FixedLength, icon); - col_widths << QTextLength(QTextLength::FixedLength, user); - col_widths << QTextLength(QTextLength::VariableLength, message); - tableFormat_.setColumnWidthConstraints(col_widths); - userColumnWidth_ = user; -} - -void ChatMessagesTextEdit::onSetClientPartyModel(const Chat::ClientPartyModelPtr& partyModel) -{ - partyModel_ = partyModel; -} - -QString ChatMessagesTextEdit::getFormattedTextFromSelection() const -{ - QString text; - QTextDocument textDocument; - - // get selected text in html format - textDocument.setHtml(createMimeDataFromSelection()->html()); - QTextBlock currentBlock = textDocument.begin(); - int blockCount = 0; - - // each column is presented as a block - while (currentBlock.isValid()) { - blockCount++; - if (!currentBlock.text().isEmpty()) { - - // format columns splits to tabulation - if (!text.isEmpty()) { - text += QChar::Tabulation; - - // new row (when few rows are selected) - if ((blockCount - 2) % 5 == 0) { - text += QChar::LineFeed; - } - } - // replace some special characters, because they can display incorrect - text += currentBlock.text().replace(QChar::LineSeparator, QChar::LineFeed); - } - currentBlock = currentBlock.next(); - } - return text; -} - -void ChatMessagesTextEdit::onUrlActivated(const QUrl &link) { - if (link.scheme() != QLatin1Literal("user")) { - QDesktopServices::openUrl(link); - } - else { - onUserUrlOpened(link); - } -} - -void ChatMessagesTextEdit::insertMessage(const Chat::MessagePtr& messagePtr) -{ - // push new message if it doesn't exist in current chat - auto& messagesList = messages_[messagePtr->partyId()]; - const auto messageIt = - std::find_if(messagesList.begin(), messagesList.end(), [messagePtr](const Chat::MessagePtr& m)->bool - { - return m->messageId() == messagePtr->messageId(); - }); - - // remove duplicates by give message_id - if (messageIt != messagesList.cend()) - { - deleteMessage(static_cast(std::distance(messagesList.begin(), messageIt))); - messagesList.erase(messageIt); - } - - messagesList.push_back(messagePtr); - if (messagePtr->partyId() == currentPartyId_) { - showMessage(messagePtr->partyId(), messagePtr->messageId()); - } - - if (messagePtr->partyMessageState() != Chat::PartyMessageState::SEEN - && messagePtr->senderHash() != ownUserId_ - && messagePtr->partyId() == currentPartyId_ - && isVisible()) { - emit messageRead(messagePtr->partyId(), messagePtr->messageId()); - } -} - -void ChatMessagesTextEdit::insertMessageInDoc(QTextCursor& cursor, const std::string& partyId, const std::string& messageId) -{ - cursor.beginEditBlock(); - auto* table = cursor.insertTable(1, 4, tableFormat_); - - const auto time = data(partyId, messageId, Column::Time); - table->cellAt(0, 0).firstCursorPosition().insertHtml(time); - - const auto image = statusImage(partyId, messageId); - if (!image.isNull()) { - table->cellAt(0, 1).firstCursorPosition().insertImage(image); - } - - const auto user = data(partyId, messageId, Column::User); - table->cellAt(0, 2).firstCursorPosition().insertHtml(user); - - const auto message = data(partyId, messageId, Column::Message); - table->cellAt(0, 3).firstCursorPosition().insertHtml(message); - cursor.endEditBlock(); -} - -void ChatMessagesTextEdit::updateMessage(const std::string& partyId, const std::string& messageId) -{ - ClientMessagesHistory messagesList = messages_[partyId]; - const ClientMessagesHistory::iterator it = - std::find_if(messagesList.begin(), messagesList.end(), [messageId](const Chat::MessagePtr& m)->bool - { - return m->messageId() == messageId; - }); - - if (it != messagesList.end()) - { - const int distance = std::distance(messagesList.begin(), it); - auto cursor = deleteMessage(distance); - insertMessageInDoc(cursor, partyId, messageId); - } -} - -QTextCursor ChatMessagesTextEdit::deleteMessage(const int index) const -{ - QTextCursor cursor = textCursor(); - cursor.movePosition(QTextCursor::Start); - cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, index * 2); - cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, 1); - cursor.removeSelectedText(); - - return cursor; -} - -QString ChatMessagesTextEdit::elideUserName(const std::string& displayName) const -{ - return fontMetrics().elidedText(QString::fromStdString(displayName), Qt::ElideRight, userColumnWidth_); -} - -void ChatMessagesTextEdit::showMessage(const std::string& partyId, const std::string& messageId) -{ - /* add text */ - QTextCursor cursor = textCursor(); - cursor.movePosition(QTextCursor::End); - - insertMessageInDoc(cursor, partyId, messageId); -} - -void ChatMessagesTextEdit::showMessages(const std::string &partyId) -{ - for (const auto& message : messages_[partyId]) { - showMessage(partyId, message->messageId()); - } -} - -std::unique_ptr ChatMessagesTextEdit::initUserContextMenu(const QString& userName) -{ - std::unique_ptr userMenuPtr = std::make_unique(this); - - Chat::ClientPartyPtr clientPartyPtr = partyModel_->getStandardPartyForUsers(ownUserId_, userName.toStdString()); - if (!clientPartyPtr) { - QAction* addAction = userMenuPtr->addAction(contextMenuAddUserMenu); - addAction->setStatusTip(contextMenuAddUserMenuStatusTip); - connect(addAction, &QAction::triggered, this, [this, userName_ = userName]() { - onShowRequestPartyBox(userName_.toStdString()); - }); - } - else { - QAction* removeAction = userMenuPtr->addAction(contextMenuRemoveUserMenu); - removeAction->setStatusTip(contextMenuRemoveUserMenuStatusTip); - connect(removeAction, &QAction::triggered, this, [this, clientPartyPtr]() { - emit removePartyRequest(clientPartyPtr->id()); - }); - } - - return userMenuPtr; -} - -void ChatMessagesTextEdit::onMessageUpdate(const Chat::MessagePtrList& messagePtrList) -{ -#ifndef QT_NO_DEBUG - const std::string& partyId = !messagePtrList.empty() ? messagePtrList[0]->partyId() : ""; -#endif - Chat::MessagePtrList messagePtrListSorted = messagePtrList; - std::sort(messagePtrListSorted.begin(), messagePtrListSorted.end(), [](const auto& left, const auto& right) -> bool { - return left->timestamp() < right->timestamp(); - }); - for (const auto& messagePtr : messagePtrListSorted) { -#ifndef QT_NO_DEBUG - Q_ASSERT(partyId == messagePtr->partyId()); -#endif - insertMessage(messagePtr); - } -} - -void ChatMessagesTextEdit::onUpdatePartyName(const std::string& partyId) -{ - const auto messageHistory = messages_[currentPartyId_]; - - for (const auto& messagePtr : messageHistory) - { - if (messagePtr->partyId() != partyId) { - continue; - } - - updateMessage(partyId, messagePtr->messageId()); - } -} - -Chat::MessagePtr ChatMessagesTextEdit::findMessage(const std::string& partyId, const std::string& messageId) -{ - if (messages_.contains(partyId)) { - const auto it = std::find_if(messages_[partyId].begin(), messages_[partyId].end(), [messageId](const Chat::MessagePtr& message) { - return message->messageId() == messageId; - }); - - if (it != messages_[partyId].end()) { - return *it; - } - } - - return {}; -} - -void ChatMessagesTextEdit::notifyMessageChanged(const Chat::MessagePtr& message) -{ - const std::string& partyId = message->partyId(); - if (partyId != currentPartyId_) { - // Do not need to update view - return; - } - - if (messages_.contains(partyId)) { - const std::string& id = message->messageId(); - const auto it = std::find_if(messages_[partyId].begin(), messages_[partyId].end(), [id](const Chat::MessagePtr& iteration) { - return iteration->messageId() == id; - }); - - if (it != messages_[partyId].end()) { - updateMessage(partyId, (*it)->messageId()); - } - } -} - -QString ChatMessagesTextEdit::toHtmlUsername(const std::string& username, const std::string& userId) const -{ - return QStringLiteral("%3") - .arg(QString::fromStdString(userId)) - .arg(internalStyle_.colorHyperlink().name()) - .arg(elideUserName(username)); -} - -QString ChatMessagesTextEdit::toHtmlInvalid(const QString &text) const -{ - QString changedText = QStringLiteral("%2").arg(internalStyle_.colorRed().name()).arg(text); - return changedText; -} - -QString ChatMessagesTextEdit::toHtmlText(const QString &text) const -{ - const auto otcText = OtcUtils::toReadableString(text); - if (!otcText.isEmpty()) { - // No further processing is needed - return QStringLiteral("*** %2 ***").arg(internalStyle_.colorOtc().name()).arg(otcText); - } - - QString changedText = text.toHtmlEscaped(); - - // make linkable - int index = 0; - int startIndex; - - while ((startIndex = changedText.indexOf(QLatin1Literal("https://"), index, Qt::CaseInsensitive)) != -1 - || (startIndex = changedText.indexOf(QLatin1Literal("http://"), index, Qt::CaseInsensitive)) != -1) { - - int endIndex = changedText.indexOf(QLatin1Literal(" "), startIndex); - if (endIndex == -1) { - endIndex = changedText.indexOf(QLatin1Literal("\n"), startIndex); - } - if (endIndex == -1) { - endIndex = changedText.length(); - } - - QString linkText = changedText.mid(startIndex, endIndex - startIndex); - QString hyperlinkText = QStringLiteral("%1").arg(linkText).arg(internalStyle_.colorHyperlink().name()); - - changedText = changedText.replace(startIndex, endIndex - startIndex, hyperlinkText); - - index = startIndex + hyperlinkText.length(); - } - - // replace linefeed with
- changedText.replace(QLatin1Literal("\n"), QLatin1Literal("
")); - - // set text color as white - changedText = QStringLiteral("%2").arg(internalStyle_.colorWhite().name()).arg(changedText); - - return changedText; -} diff --git a/BlockSettleUILib/ChatUI/ChatMessagesTextEdit.h b/BlockSettleUILib/ChatUI/ChatMessagesTextEdit.h deleted file mode 100644 index 0f6319bac..000000000 --- a/BlockSettleUILib/ChatUI/ChatMessagesTextEdit.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATMESSAGESTEXTEDIT_H -#define CHATMESSAGESTEXTEDIT_H - -#include "ChatProtocol/Message.h" -#include "ChatProtocol/ClientPartyModel.h" - -#include -#include -#include -#include -#include - -#include - -namespace Chat { - class MessageData; -} - -class ChatMessagesTextEditStyle : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor color_hyperlink READ colorHyperlink - WRITE setColorHyperlink) - Q_PROPERTY(QColor color_white READ colorWhite - WRITE setColorWhite) - Q_PROPERTY(QColor color_red READ colorRed - WRITE setColorRed) - Q_PROPERTY(QColor color_otc READ colorOtc - WRITE setColorOtc) - -public: - explicit ChatMessagesTextEditStyle(QWidget *parent) - : QWidget(parent), colorHyperlink_(Qt::blue), colorWhite_(Qt::white), colorRed_(Qt::red), colorOtc_(Qt::lightGray) - { - QWidget::setVisible(false); - } - - QColor colorHyperlink() const { return colorHyperlink_; } - void setColorHyperlink(const QColor &colorHyperlink) { - colorHyperlink_ = colorHyperlink; - } - - QColor colorWhite() const { return colorWhite_; } - void setColorWhite(const QColor &colorWhite) { - colorWhite_ = colorWhite; - } - - QColor colorRed() const { return colorRed_; } - void setColorRed(QColor val) { colorRed_ = val; } - - QColor colorOtc() const { return colorOtc_; } - void setColorOtc(const QColor &colorOtc) { - colorOtc_ = colorOtc; - } - -private: - QColor colorHyperlink_; - QColor colorWhite_; - QColor colorRed_; - QColor colorOtc_; -}; - -class ChatMessagesTextEdit : public QTextBrowser -{ - Q_OBJECT - -public: - ChatMessagesTextEdit(QWidget* parent = nullptr); - ~ChatMessagesTextEdit() noexcept override = default; - - QString getFormattedTextFromSelection() const; - int messagesCount(const std::string& partyId) const; - -public slots: - void onSetColumnsWidth(int time, int icon, int user, int message); - void onSetOwnUserId(const std::string &userId) { ownUserId_ = userId; } - void onSetClientPartyModel(const Chat::ClientPartyModelPtr& partyModel); - void onSwitchToChat(const std::string& partyId); - void onLogout(); - Chat::MessagePtr onMessageStatusChanged(const std::string& partyId, const std::string& message_id, - const int party_message_state); - void onMessageUpdate(const Chat::MessagePtrList& messagePtrList); - void onUpdatePartyName(const std::string& partyId); - - void onShowRequestPartyBox(const std::string& userHash); - -signals: - void messageRead(const std::string& partyId, const std::string& messageId); - void newPartyRequest(const std::string& userName, const std::string& initialMessage); - void removePartyRequest(const std::string& partyId); - void switchPartyRequest(const QString& partyId); - -protected: - enum class Column { - Time, - Status, - User, - Message, - last - }; - - QString data(const std::string& partyId, const std::string& messageId, const Column &column); - QString dataMessage(const std::string& partyId, const std::string& messageId, const Column &column) const; - QImage statusImage(const std::string& partyId, const std::string& messageId) const; - - void contextMenuEvent(QContextMenuEvent* e) override; - -private slots: - void onUrlActivated(const QUrl &link); - void onCopyActionTriggered() const; - void onCopyLinkLocationActionTriggered() const; - void onSelectAllActionTriggered(); - void onTextChanged() const; - void onUserUrlOpened(const QUrl &url); - -private: - Chat::MessagePtr getMessage(const std::string& partyId, const std::string& messageId) const; - void setupHighlightPalette(); - std::unique_ptr initUserContextMenu(const QString& userName); - - // #new_logic - QString toHtmlUsername(const std::string& username, const std::string& userId) const; - QString toHtmlText(const QString &text) const; - QString toHtmlInvalid(const QString &text) const; - - void insertMessage(const Chat::MessagePtr& messagePtr); - void showMessage(const std::string& partyId, const std::string& messageId); - void showMessages(const std::string& partyId); - Chat::MessagePtr findMessage(const std::string& partyId, const std::string& messageId); - void notifyMessageChanged(const Chat::MessagePtr& message); - void insertMessageInDoc(QTextCursor& cursor, const std::string& partyId, const std::string& messageId); - void updateMessage(const std::string& partyId, const std::string& messageId); - QTextCursor deleteMessage(int index) const; - QString elideUserName(const std::string& displayName) const; - - Chat::ClientPartyModelPtr partyModel_; - - std::string currentPartyId_; - std::string ownUserId_; - - using ClientMessagesHistory = QVector; - QMap messages_; - - QImage statusImageGreyUnsent_ = QImage({ QLatin1Literal(":/ICON_MSG_STATUS_OFFLINE") }, "PNG"); - QImage statusImageYellowSent_ = QImage({ QLatin1Literal(":/ICON_MSG_STATUS_CONNECTING") }, "PNG"); - QImage statusImageGreenReceived_ = QImage({ QLatin1Literal(":/ICON_MSG_STATUS_ONLINE") }, "PNG"); - QImage statusImageBlueSeen_ = QImage({ QLatin1Literal(":/ICON_MSG_STATUS_READ") }, "PNG"); - - QTextTableFormat tableFormat_; - ChatMessagesTextEditStyle internalStyle_; - - QTextCursor textCursor_; - QString anchor_; - int userColumnWidth_ = 0; -}; - -#endif // CHATMESSAGESTEXTEDIT_H diff --git a/BlockSettleUILib/ChatUI/ChatOTCHelper.cpp b/BlockSettleUILib/ChatUI/ChatOTCHelper.cpp deleted file mode 100644 index 221d04157..000000000 --- a/BlockSettleUILib/ChatUI/ChatOTCHelper.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatOTCHelper.h" - -#include - -#include "ArmoryConnection.h" -#include "ChatProtocol/ClientParty.h" -#include "OtcClient.h" -#include "OtcUtils.h" -#include "SignContainer.h" -#include "chat.pb.h" -#include "UtxoReservationManager.h" - -ChatOTCHelper::ChatOTCHelper(QObject* parent /*= nullptr*/) - : QObject(parent) -{ -} - -void ChatOTCHelper::init(bs::network::otc::Env env - , const std::shared_ptr& loggerPtr - , const std::shared_ptr& walletsMgr - , const std::shared_ptr& armory - , const std::shared_ptr& signContainer - , const std::shared_ptr &authAddressManager - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr& applicationSettings) -{ - loggerPtr_ = loggerPtr; - - OtcClientParams params; - params.env = env; - otcClient_ = new OtcClient(loggerPtr, walletsMgr, armory, signContainer, authAddressManager, - utxoReservationManager, applicationSettings, std::move(params), this); -} - -OtcClient* ChatOTCHelper::client() const -{ - return otcClient_; -} - -void ChatOTCHelper::setCurrentUserId(const std::string& ownUserId) -{ - otcClient_->setOwnContactId(ownUserId); -} - -void ChatOTCHelper::setGlobalOTCEntryTimeStamp(QDateTime timeStamp) -{ - selectedGlobalEntryTimeStamp_ = timeStamp; -} - -QDateTime ChatOTCHelper::selectedGlobalOTCEntryTimeStamp() const -{ - return selectedGlobalEntryTimeStamp_; -} - -void ChatOTCHelper::onLogout() -{ - for (const auto &contactId : connectedContacts_) { - otcClient_->contactDisconnected(contactId); - } - connectedContacts_.clear(); -} - -void ChatOTCHelper::onProcessOtcPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response) -{ - otcClient_->processPbMessage(response); -} - -void ChatOTCHelper::onOtcRequestSubmit(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer& offer) -{ - if (!peer) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "peer not found"); - return; - } - - bool result = otcClient_->sendOffer(peer, offer); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "send offer failed"); - return; - } -} - -void ChatOTCHelper::onOtcPullOrReject(const bs::network::otc::PeerPtr &peer) -{ - if (!peer) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "peer not found"); - return; - } - - bool result = otcClient_->pullOrReject(peer); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "pull or reject failed"); - return; - } -} - -void ChatOTCHelper::onOtcResponseAccept(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer& offer) -{ - if (!peer) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "peer not found"); - return; - } - - bool result = otcClient_->acceptOffer(peer, offer); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "accept offer failed"); - return; - } -} - -void ChatOTCHelper::onOtcResponseUpdate(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer& offer) -{ - if (!peer) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "peer not found"); - return; - } - - bool result = otcClient_->updateOffer(peer, offer); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "update offer failed"); - return; - } -} - -void ChatOTCHelper::onOtcResponseReject(const bs::network::otc::PeerPtr &peer) -{ - if (!peer) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "peer not found"); - return; - } - - bool result = otcClient_->pullOrReject(peer); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "reject offer failed"); - return; - } -} - -void ChatOTCHelper::onOtcQuoteRequestSubmit(const bs::network::otc::QuoteRequest &request) -{ - bool result = otcClient_->sendQuoteRequest(request); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "sending quote request failed"); - return; - } -} - -void ChatOTCHelper::onOtcQuoteResponseSubmit(const bs::network::otc::PeerPtr &peer, const bs::network::otc::QuoteResponse &response) -{ - if (!peer) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "peer not found"); - return; - } - - bool result = otcClient_->sendQuoteResponse(peer, response); - if (!result) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "sending response failed"); - return; - } -} - -void ChatOTCHelper::onMessageArrived(const Chat::MessagePtrList& messagePtr) -{ - for (const auto &msg : messagePtr) { - if (msg->partyId() == Chat::OtcRoomName) { - auto data = OtcUtils::deserializePublicMessage(msg->messageText()); - if (!data.empty()) { - otcClient_->processPublicMessage(msg->timestamp(), msg->senderHash(), data); - } - } else if (msg->partyMessageState() == Chat::SENT && msg->senderHash() != otcClient_->ownContactId()) { - auto connIt = connectedContacts_.find(msg->senderHash()); - if (connIt == connectedContacts_.end()) { - continue; - } - - auto data = OtcUtils::deserializeMessage(msg->messageText()); - if (!data.empty()) { - otcClient_->processContactMessage(msg->senderHash(), data); - } - } - } -} - -void ChatOTCHelper::onPartyStateChanged(const Chat::ClientPartyPtr& clientPartyPtr) -{ - const std::string& contactId = clientPartyPtr->userHash(); - auto connIt = connectedContacts_.find(contactId); - if (clientPartyPtr->clientStatus() == Chat::ONLINE && connIt == connectedContacts_.end()) { - otcClient_->contactConnected(contactId); - connectedContacts_.insert(contactId); - } else if (clientPartyPtr->clientStatus() == Chat::OFFLINE && connIt != connectedContacts_.end()) { - otcClient_->contactDisconnected(contactId); - connectedContacts_.erase(connIt); - } -} diff --git a/BlockSettleUILib/ChatUI/ChatOTCHelper.h b/BlockSettleUILib/ChatUI/ChatOTCHelper.h deleted file mode 100644 index accae997a..000000000 --- a/BlockSettleUILib/ChatUI/ChatOTCHelper.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATOTCHELPER_H -#define CHATOTCHELPER_H - -#include -#include -#include "OtcTypes.h" -#include "ChatProtocol/Message.h" -#include "ChatProtocol/ClientParty.h" - -namespace spdlog { - class logger; -} - -namespace bs { - namespace sync { - class WalletsManager; - } - namespace network { - namespace otc { - enum class Env : int; - struct Offer; - struct Peer; - struct PeerId; - struct QuoteRequest; - struct QuoteResponse; - } - } - class UTXOReservationManager; -} - -namespace Blocksettle { - namespace Communication { - namespace ProxyTerminalPb { - class Response; - } - } -} - -class ApplicationSettings; -class ArmoryConnection; -class AuthAddressManager; -class OtcClient; -class WalletSignerContainer; - -class ChatOTCHelper : public QObject { - Q_OBJECT -public: - ChatOTCHelper(QObject* parent = nullptr); - ~ChatOTCHelper() override = default; - - void init(bs::network::otc::Env env - , const std::shared_ptr& loggerPtr - , const std::shared_ptr& walletsMgr - , const std::shared_ptr& armory - , const std::shared_ptr& signContainer - , const std::shared_ptr &authAddressManager - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr& applicationSettings); - - OtcClient* client() const; - - void setCurrentUserId(const std::string& ownUserId); - - void setGlobalOTCEntryTimeStamp(QDateTime timeStamp); - QDateTime selectedGlobalOTCEntryTimeStamp() const; - -public slots: - void onLogout(); - void onProcessOtcPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response); - - void onOtcRequestSubmit(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer& offer); - void onOtcPullOrReject(const bs::network::otc::PeerPtr &peer); - void onOtcResponseAccept(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer& offer); - void onOtcResponseUpdate(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer& offer); - void onOtcResponseReject(const bs::network::otc::PeerPtr &peer); - - void onOtcQuoteRequestSubmit(const bs::network::otc::QuoteRequest &request); - void onOtcQuoteResponseSubmit(const bs::network::otc::PeerPtr &peer, const bs::network::otc::QuoteResponse &response); - - void onMessageArrived(const Chat::MessagePtrList& messagePtr); - void onPartyStateChanged(const Chat::ClientPartyPtr& clientPartyPtr); - -private: - OtcClient* otcClient_{}; - std::set connectedContacts_; - std::shared_ptr loggerPtr_; - QDateTime selectedGlobalEntryTimeStamp_{}; -}; - -#endif // CHATOTCHELPER_H diff --git a/BlockSettleUILib/ChatUI/ChatPartiesSortProxyModel.cpp b/BlockSettleUILib/ChatUI/ChatPartiesSortProxyModel.cpp deleted file mode 100644 index a71708d47..000000000 --- a/BlockSettleUILib/ChatUI/ChatPartiesSortProxyModel.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatPartiesSortProxyModel.h" - -using namespace bs; - -ChatPartiesSortProxyModel::ChatPartiesSortProxyModel(ChatPartiesTreeModelPtr sourceModel, QObject *parent /*= nullptr*/) - : QSortFilterProxyModel(parent) - , sourceModel_(std::move(sourceModel)) -{ - setDynamicSortFilter(true); - setSourceModel(sourceModel_.get()); -} - -PartyTreeItem* ChatPartiesSortProxyModel::getInternalData(const QModelIndex& index) const -{ - if (!index.isValid()) { - return {}; - } - - const auto& sourceIndex = mapToSource(index); - return static_cast(sourceIndex.internalPointer()); -} - -const std::string& ChatPartiesSortProxyModel::currentUser() const -{ - return sourceModel_->currentUser(); -} - -Qt::ItemFlags ChatPartiesSortProxyModel::flags(const QModelIndex& index) const -{ - if (!index.isValid()) { - return Qt::NoItemFlags; - } - - PartyTreeItem* treeItem = getInternalData(index); - if (!treeItem) { - return Qt::NoItemFlags; - } - - if (UI::ElementType::Container != treeItem->modelType()) { - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; - } - - return Qt::NoItemFlags; -} - -QModelIndex ChatPartiesSortProxyModel::getProxyIndexById(const std::string& partyId) const -{ - const QModelIndex sourceIndex = sourceModel_->getPartyIndexById(partyId); - return mapFromSource(sourceIndex); -} - -QModelIndex ChatPartiesSortProxyModel::getOTCGlobalRoot() const -{ - const QModelIndex sourceOtcIndex = sourceModel_->getOTCGlobalRoot(); - return mapFromSource(sourceOtcIndex); -} - -bool ChatPartiesSortProxyModel::filterAcceptsRow(int row, const QModelIndex& parent) const -{ - Q_ASSERT(sourceModel_); - - auto index = sourceModel_->index(row, 0, parent); - if (!index.isValid()) { - return false; - } - - PartyTreeItem* item = static_cast(index.internalPointer()); - if (!item) { - return false; - } - - // return true if you want to display tree item - switch (item->modelType()) { - case UI::ElementType::Party: - return true; - case UI::ElementType::Container: { - if (item->childCount() == 0 && item->data().toString() == ChatModelNames::ContainerTabOTCIdentifier) { - return false; - } - return true; - } - default: - return false; - } -} - -bool ChatPartiesSortProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - if (!left.isValid() || !right.isValid()) { - return QSortFilterProxyModel::lessThan(left, right); - } - - PartyTreeItem* itemLeft = static_cast(left.internalPointer()); - PartyTreeItem* itemRight = static_cast(right.internalPointer()); - - if (!itemLeft || !itemRight) { - return QSortFilterProxyModel::lessThan(left, right); - } - - if (itemLeft->modelType() == itemRight->modelType()) { - if (itemLeft->modelType() == UI::ElementType::Party) { - Chat::ClientPartyPtr leftParty = itemLeft->data().value(); - Chat::ClientPartyPtr rightParty = itemRight->data().value(); - return leftParty->displayName() < rightParty->displayName(); - } - - if (itemLeft->modelType() == UI::ElementType::Container) { - return itemLeft->childNumber() < itemRight->childNumber(); - } - } - - return QSortFilterProxyModel::lessThan(left, right); -} diff --git a/BlockSettleUILib/ChatUI/ChatPartiesSortProxyModel.h b/BlockSettleUILib/ChatUI/ChatPartiesSortProxyModel.h deleted file mode 100644 index 5db53f4f8..000000000 --- a/BlockSettleUILib/ChatUI/ChatPartiesSortProxyModel.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATPARTYSORTPROXYMODEL_H -#define CHATPARTYSORTPROXYMODEL_H - -#include "ChatPartiesTreeModel.h" -#include - -class ChatPartiesSortProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT -public: - explicit ChatPartiesSortProxyModel(ChatPartiesTreeModelPtr sourceModel, QObject *parent = nullptr); - - PartyTreeItem* getInternalData(const QModelIndex& index) const; - - const std::string& currentUser() const; - Qt::ItemFlags flags(const QModelIndex& index) const override; - - QModelIndex getProxyIndexById(const std::string& partyId) const; - QModelIndex getOTCGlobalRoot() const; - -protected: - - bool filterAcceptsRow(int row, const QModelIndex& parent) const override; - bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; - -private: - ChatPartiesTreeModelPtr sourceModel_; -}; - -using ChatPartiesSortProxyModelPtr = std::shared_ptr; - -#endif // CHATPARTYSORTPROXYMODEL_H diff --git a/BlockSettleUILib/ChatUI/ChatPartiesTreeModel.cpp b/BlockSettleUILib/ChatUI/ChatPartiesTreeModel.cpp deleted file mode 100644 index f4df3d831..000000000 --- a/BlockSettleUILib/ChatUI/ChatPartiesTreeModel.cpp +++ /dev/null @@ -1,511 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatPartiesTreeModel.h" - -#include "OtcClient.h" - -using namespace bs; -using namespace bs::network; - -namespace { - const auto kTogglingIntervalMs = std::chrono::milliseconds(250); -} - -ChatPartiesTreeModel::ChatPartiesTreeModel(const Chat::ChatClientServicePtr& chatClientServicePtr, OtcClient *otcClient, QObject* parent) - : QAbstractItemModel(parent) - , chatClientServicePtr_(chatClientServicePtr) - , otcClient_(otcClient) -{ - rootItem_ = new PartyTreeItem({}, UI::ElementType::Root); - - // #flickeringOTC Flickering otc awaiting messages - disabled for now - //otcWatchToggling_.setInterval(kTogglingIntervalMs); - //connect(&otcWatchToggling_, &QTimer::timeout, this, &ChatPartiesTreeModel::onUpdateOTCAwaitingColor); - //otcWatchToggling_.start(); -} - -ChatPartiesTreeModel::~ChatPartiesTreeModel() = default; - -void ChatPartiesTreeModel::onPartyModelChanged() -{ - Chat::ClientPartyModelPtr clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - - // Save unseen state first - const auto reusableItemData = collectReusableData(rootItem_); - - beginResetModel(); - - rootItem_->removeAll(); - otcWatchIndx_.clear(); - - std::unique_ptr globalSection = std::make_unique(ChatModelNames::ContainerTabGlobal, UI::ElementType::Container, rootItem_); - std::unique_ptr otcGlobalSection = std::make_unique(ChatModelNames::ContainerTabOTCIdentifier, UI::ElementType::Container, rootItem_); - std::unique_ptr privateSection = std::make_unique(ChatModelNames::ContainerTabPrivate, UI::ElementType::Container, rootItem_); - std::unique_ptr requestSection = std::make_unique(ChatModelNames::ContainerTabContactRequest, UI::ElementType::Container, rootItem_); - - auto insertChild = [](PartyTreeItem* section, QVariant stored) -> PartyTreeItem* { - std::unique_ptr partyTreeItem = std::make_unique(stored, UI::ElementType::Party, section); - PartyTreeItem* pTreeItem = partyTreeItem.get(); - section->insertChildren(std::move(partyTreeItem)); - return pTreeItem; - }; - - const auto idPartyList = clientPartyModelPtr->getIdPartyList(); - const auto clientPartyPtrList = clientPartyModelPtr->getClientPartyListFromIdPartyList(idPartyList); - - for (const auto& clientPartyPtr : clientPartyPtrList) { - assert(clientPartyPtr); - - QVariant stored; - stored.setValue(clientPartyPtr); - - if (clientPartyPtr->isGlobalOTC()) { - insertChild(otcGlobalSection.get(), stored); - } - else if (clientPartyPtr->isGlobal()) { - insertChild(globalSection.get(), stored); - } - else if (clientPartyPtr->isPrivateStandard()) { - if (clientPartyPtr->partyState() == Chat::PartyState::REJECTED) { - continue; - } - - PartyTreeItem* parentSection = clientPartyPtr->partyState() == Chat::PartyState::INITIALIZED ? privateSection.get() : requestSection.get(); - PartyTreeItem* newChild = insertChild(parentSection, stored); - - assert(newChild); - auto it = reusableItemData.find(clientPartyPtr->id()); - if (it != reusableItemData.end()) { - newChild->applyReusableData(it.value()); - } - } - } - - rootItem_->insertChildren(std::move(globalSection)); - rootItem_->insertChildren(std::move(otcGlobalSection)); - rootItem_->insertChildren(std::move(privateSection)); - rootItem_->insertChildren(std::move(requestSection)); - - endResetModel(); - emit restoreSelectedIndex(); - - if (!reusableItemData.isEmpty()) { - resetOTCUnseen({}); - } - - onGlobalOTCChanged(reusableItemData); -} - -void ChatPartiesTreeModel::onGlobalOTCChanged(QMap reusableItemData /* = {} */) -{ - QModelIndex otcGlobalModelIndex = getOTCGlobalRoot(); - if (!otcGlobalModelIndex.isValid()) { - return; - } - - PartyTreeItem* otcParty = static_cast(otcGlobalModelIndex.internalPointer()); - if (reusableItemData.isEmpty()) { - reusableItemData = collectReusableData(otcParty); - } - - resetOTCUnseen(otcGlobalModelIndex, false, false); - if (otcParty->childCount() > 0) { - beginRemoveRows(otcGlobalModelIndex, 0, otcParty->childCount() - 1); - otcParty->removeAll(); - endRemoveRows(); - } - - auto fAddOtcParty = [this, &reusableItemData](const bs::network::otc::PeerPtr &peer, std::unique_ptr& section, otc::PeerType peerType) { - Chat::ClientPartyModelPtr clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - Chat::ClientPartyPtr otcPartyPtr = clientPartyModelPtr->getOtcPartyForUsers(currentUser(), peer->contactId); - if (!otcPartyPtr) { - return; - } - QVariant stored; - stored.setValue(otcPartyPtr); - - std::unique_ptr otcItem = std::make_unique(stored, UI::ElementType::Party, section.get()); - otcItem->peerType = peerType; - - auto it = reusableItemData.find(otcPartyPtr->id()); - if (it != reusableItemData.end()) { - otcItem->applyReusableData(it.value()); - } - - section->insertChildren(std::move(otcItem)); - }; - - beginInsertRows(otcGlobalModelIndex, 0, 1); - - std::unique_ptr sentSection = std::make_unique(ChatModelNames::TabOTCSentRequest, UI::ElementType::Container, otcParty); - for (const auto &peer : otcClient_->requests()) { - // Show only responded requests here - if (peer->state != otc::State::Idle) { - fAddOtcParty(peer, sentSection, otc::PeerType::Request); - } - } - otcParty->insertChildren(std::move(sentSection)); - - std::unique_ptr responseSection = std::make_unique(ChatModelNames::TabOTCReceivedResponse, UI::ElementType::Container, otcParty); - for (const auto &peer : otcClient_->responses()) { - fAddOtcParty(peer, responseSection, otc::PeerType::Response); - } - - otcParty->insertChildren(std::move(responseSection)); - - endInsertRows(); - emit restoreSelectedIndex(); - if (!reusableItemData.isEmpty()) { - resetOTCUnseen(otcGlobalModelIndex, true, false); - } -} - -void ChatPartiesTreeModel::onCleanModel() -{ - beginResetModel(); - rootItem_->removeAll(); - endResetModel(); -} - -void ChatPartiesTreeModel::onPartyStatusChanged(const Chat::ClientPartyPtr& clientPartyPtr) -{ - const QModelIndex partyIndex = getPartyIndexById(clientPartyPtr->id()); - - if (partyIndex.isValid()) { - emit dataChanged(partyIndex, partyIndex); - } -} - -void ChatPartiesTreeModel::onIncreaseUnseenCounter(const std::string& partyId, int newMessageCount, bool isUnseenOTCMessage /* = false */) -{ - const QModelIndex partyIndex = getPartyIndexById(partyId); - if (!partyIndex.isValid()) { - return; - } - - PartyTreeItem* partyItem = static_cast(partyIndex.internalPointer()); - partyItem->increaseUnseenCounter(newMessageCount); - - if (isUnseenOTCMessage) { - otcWatchIndx_.insert({ partyIndex }); - partyItem->enableOTCToggling(isUnseenOTCMessage); - } -} - -void ChatPartiesTreeModel::onDecreaseUnseenCounter(const std::string& partyId, int seenMessageCount) -{ - const QModelIndex partyIndex = getPartyIndexById(partyId); - if (!partyIndex.isValid()) { - return; - } - - PartyTreeItem* partyItem = static_cast(partyIndex.internalPointer()); - partyItem->decreaseUnseenCounter(seenMessageCount); - - - if (partyItem->isOTCTogglingMode()) { - partyItem->enableOTCToggling(false); - otcWatchIndx_.remove({ partyIndex }); - } -} - -void ChatPartiesTreeModel::onUpdateOTCAwaitingColor() -{ - if (otcWatchIndx_.isEmpty()) { - return; - } - - for (const auto& index : otcWatchIndx_) { - if (index.isValid()) { - PartyTreeItem* partyItem = static_cast(index.internalPointer()); - partyItem->changeOTCToggleState(); - - emit dataChanged(index, index, { Qt::DecorationRole }); - } - } -} - -const QModelIndex ChatPartiesTreeModel::getPartyIndexById(const std::string& partyId, const QModelIndex parent) const -{ - PartyTreeItem* parentItem = nullptr; - if (parent.isValid()) { - parentItem = static_cast(parent.internalPointer()); - } - else { - parentItem = rootItem_; - } - Q_ASSERT(parentItem); - - QList> itemsToCheck; - itemsToCheck.push_back({ parent , parentItem }); - - QPair currentPair; - while (!itemsToCheck.isEmpty()) { - currentPair = itemsToCheck[0]; - itemsToCheck.pop_front(); - - QModelIndex iterModelIndex = currentPair.first; - PartyTreeItem* iterItem= currentPair.second; - - - for (int iChild = 0; iChild < iterItem->childCount(); ++iChild) { - auto* child = iterItem->child(iChild); - - auto childIndex = index(iChild, 0, iterModelIndex); - if (child->modelType() == UI::ElementType::Container && child->data().canConvert()) { - if (child->data().toString().toStdString() == partyId) { - return childIndex; - } - } - else if (child->modelType() == UI::ElementType::Party && child->data().canConvert()) { - const Chat::ClientPartyPtr clientPtr = child->data().value(); - - if (!clientPtr) { - return {}; - } - - if (clientPtr->id() == partyId) { - return childIndex; - } - } - - if (child->childCount() != 0) { - itemsToCheck.push_back({ childIndex , child }); - } - } - - } - - return {}; -} - -PartyTreeItem* ChatPartiesTreeModel::getItem(const QModelIndex& index) const -{ - if (index.isValid()) { - PartyTreeItem* item = static_cast(index.internalPointer()); - if (item) { - return item; - } - } - - return rootItem_; -} - -void ChatPartiesTreeModel::forAllPartiesInModel(PartyTreeItem* parent, - std::function&& applyFunc) const -{ - QList itemsToCheck; - itemsToCheck.push_back(parent ? parent : rootItem_); - - while (!itemsToCheck.isEmpty()) { - PartyTreeItem* item = itemsToCheck[0]; - itemsToCheck.pop_front(); - - applyFunc(item); - - for (int i = 0; i < item->childCount(); ++i) { - itemsToCheck.push_back(item->child(i)); - } - } -} - -void ChatPartiesTreeModel::forAllIndexesInModel(const QModelIndex& parentIndex, - std::function&& applyFunc) const -{ - PartyTreeItem* item = nullptr; - if (!parentIndex.isValid()) { - item = rootItem_; - } - else { - item = static_cast(parentIndex.internalPointer()); - } - - QList itemsToCheck; - if (parentIndex.isValid()) { - itemsToCheck.push_back(parentIndex); - } - else { // root item - for (int i = 0; i < rootItem_->childCount(); ++i) { - itemsToCheck.push_back(index(i, 0)); - } - } - - while (!itemsToCheck.isEmpty()) { - QModelIndex itemIndex = std::move(itemsToCheck[0]); - itemsToCheck.pop_front(); - - applyFunc(itemIndex); - - item = static_cast(itemIndex.internalPointer()); - for (int i = 0; i < item->childCount(); ++i) { - itemsToCheck.push_back(index(i, 0, itemIndex)); - } - } -} - -QMap ChatPartiesTreeModel::collectReusableData(PartyTreeItem* parent) -{ - QMap reusableData; - forAllPartiesInModel(parent, [&](const PartyTreeItem* party) { - if (party->modelType() != UI::ElementType::Party) { - return; - } - - const Chat::ClientPartyPtr clientPtr = party->data().value(); - - if (party->unseenCount() != 0) { - reusableData.insert(clientPtr->id(), party->generateReusableData()); - } - }); - - return reusableData; -} - -void ChatPartiesTreeModel::resetOTCUnseen(const QModelIndex& parentIndex, - bool isAddChildren /*= true*/, bool isClearAll /*= true*/) -{ - if (isClearAll) { - otcWatchIndx_.clear(); - } - - forAllIndexesInModel(parentIndex, [&](const QModelIndex& index) { - PartyTreeItem* item = static_cast(index.internalPointer()); - if (item->modelType() != UI::ElementType::Party) { - return; - } - - if (item->isOTCTogglingMode()) { - if (isAddChildren) { - otcWatchIndx_.insert({ index }); - } - else { - otcWatchIndx_.remove({ index }); - } - - } - }); -} - -QModelIndex ChatPartiesTreeModel::getOTCGlobalRoot() const -{ - for (int iContainer = 0; iContainer < rootItem_->childCount(); ++iContainer) { - auto* container = rootItem_->child(iContainer); - - Q_ASSERT(container->data().canConvert()); - if (container->data().toString() != ChatModelNames::ContainerTabOTCIdentifier) { - continue; - } - - for (int iParty = 0; iParty < container->childCount(); ++iParty) { - const PartyTreeItem* party = container->child(iParty); - if (party->data().canConvert()) { - const Chat::ClientPartyPtr clientPtr = party->data().value(); - if (clientPtr->isGlobalOTC()) { - return index(iParty, 0, index(iContainer, 0)); - } - } - } - - return {}; - } - - return {}; -} - -QVariant ChatPartiesTreeModel::data(const QModelIndex& index, int role) const -{ - if (!index.isValid()) { - return QVariant(); - } - - if (role != Qt::DisplayRole) { - return QVariant(); - } - - PartyTreeItem* item = getItem(index); - - if (item->modelType() == UI::ElementType::Container) { - if (item->data().toString() == ChatModelNames::ContainerTabOTCIdentifier) { - return { ChatModelNames::ContainerTabOTCDisplayName }; - } - - return item->data(); - } - else if (item->modelType() == UI::ElementType::Party) { - Q_ASSERT(item->data().canConvert()); - Chat::ClientPartyPtr clientPartyPtr = item->data().value(); - if (!clientPartyPtr) { - return QVariant(); - } - - if (clientPartyPtr->isGlobalOTC()) { - return { ChatModelNames::PrivatePartyGlobalOTCDisplayName }; - } - return QString::fromStdString(clientPartyPtr->displayName()); - } - - return {}; -} - -QModelIndex ChatPartiesTreeModel::index(int row, int column, const QModelIndex& parent) const -{ - if (parent.isValid() && parent.column() != 0) { - return QModelIndex(); - } - - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } - - PartyTreeItem* parentItem = getItem(parent); - Q_ASSERT(parentItem); - - PartyTreeItem* childItem = parentItem->child(row); - if (childItem) { - return createIndex(row, column, childItem); - } - - return QModelIndex(); -} - -QModelIndex ChatPartiesTreeModel::parent(const QModelIndex& index) const -{ - if (!index.isValid()) { - return QModelIndex(); - } - - PartyTreeItem* childItem = getItem(index); - PartyTreeItem* parentItem = childItem->parent(); - - if (parentItem == rootItem_) { - return QModelIndex(); - } - - return createIndex(parentItem->childNumber(), 0, parentItem); -} - -int ChatPartiesTreeModel::rowCount(const QModelIndex& parent) const -{ - PartyTreeItem* parentItem = getItem(parent); - - return parentItem->childCount(); -} - -int ChatPartiesTreeModel::columnCount(const QModelIndex& parent) const -{ - return rootItem_->columnCount(); -} - -const std::string& ChatPartiesTreeModel::currentUser() const -{ - const auto chatModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - return chatModelPtr->ownUserName(); -} diff --git a/BlockSettleUILib/ChatUI/ChatPartiesTreeModel.h b/BlockSettleUILib/ChatUI/ChatPartiesTreeModel.h deleted file mode 100644 index 03560d2c9..000000000 --- a/BlockSettleUILib/ChatUI/ChatPartiesTreeModel.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATPARTYLISTMODEL_H -#define CHATPARTYLISTMODEL_H - -#include -#include "ChatProtocol/ChatClientService.h" -#include "PartyTreeItem.h" - -class OtcClient; -class ChatPartiesTreeModel : public QAbstractItemModel -{ - Q_OBJECT -public: - ChatPartiesTreeModel(const Chat::ChatClientServicePtr& chatClientServicePtr, OtcClient *otcClient - , QObject* parent = nullptr); - ~ChatPartiesTreeModel() override; - - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - const std::string& currentUser() const; - - QModelIndex getOTCGlobalRoot() const; - const QModelIndex getPartyIndexById(const std::string& partyId, const QModelIndex parent = {}) const; - -signals: - void restoreSelectedIndex(); - -public slots: - void onPartyModelChanged(); - void onGlobalOTCChanged(QMap reusableItemData = {}); - void onCleanModel(); - void onPartyStatusChanged(const Chat::ClientPartyPtr& clientPartyPtr); - void onIncreaseUnseenCounter(const std::string& partyId, int newMessageCount, bool isUnseenOTCMessage = false); - void onDecreaseUnseenCounter(const std::string& partyId, int seenMessageCount); - -private slots: - void onUpdateOTCAwaitingColor(); - -private: - PartyTreeItem* getItem(const QModelIndex& index) const; - void forAllPartiesInModel(PartyTreeItem* parent, std::function&& applyFunc) const; - void forAllIndexesInModel(const QModelIndex& parent, std::function&& applyFunc) const; - QMap collectReusableData(PartyTreeItem* parent); - void resetOTCUnseen(const QModelIndex& parentIndex, bool isAddChildren = true, bool isClearAll = true); - - PartyTreeItem* rootItem_{}; - - Chat::ChatClientServicePtr chatClientServicePtr_; - OtcClient* otcClient_{}; - - QSet otcWatchIndx_; - QTimer otcWatchToggling_; -}; - -using ChatPartiesTreeModelPtr = std::shared_ptr; - -namespace ChatModelNames { - const QString ContainerTabGlobal = QObject::tr("Public"); - const QString ContainerTabPrivate = QObject::tr("Private"); - const QString ContainerTabContactRequest = QObject::tr("Contact request"); - - const QString ContainerTabOTCIdentifier = QObject::tr("C_OTC"); - const QString ContainerTabOTCDisplayName = QObject::tr("OTC"); - - const QString PrivatePartyGlobalOTCDisplayName = QObject::tr("Global"); - - // OTC - const QString TabOTCSentRequest = QObject::tr("Submitted quotes"); - const QString TabOTCReceivedResponse = QObject::tr("Received quotes"); -} - -#endif // CHATPARTYLISTMODEL_H diff --git a/BlockSettleUILib/ChatUI/ChatSearchLineEdit.cpp b/BlockSettleUILib/ChatUI/ChatSearchLineEdit.cpp deleted file mode 100644 index 226bcc48b..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchLineEdit.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatSearchLineEdit.h" - -#include -ChatSearchLineEdit::ChatSearchLineEdit(QWidget *parent) - : QLineEdit(parent) - , resetOnNextInput_(false) -{ - connect(this, &QLineEdit::textChanged, this, &ChatSearchLineEdit::onTextChanged); -} - -/* -void ChatSearchLineEdit::setActionsHandler(std::shared_ptr handler) -{ - handler_ = handler; -} -*/ - -void ChatSearchLineEdit::setResetOnNextInput(bool value) -{ - resetOnNextInput_ = value; -} - -void ChatSearchLineEdit::onTextChanged(const QString &text) -{ -/* - if (text.isEmpty() && handler_) { - handler_->onActionResetSearch(); - } -*/ -} - - -ChatSearchLineEdit::~ChatSearchLineEdit() = default; - -void ChatSearchLineEdit::keyPressEvent(QKeyEvent * e) -{ - switch (e->key()) { - case Qt::Key_Enter: //Qt::Key_Enter - Numpad Enter key - case Qt::Key_Return: //Qt::Key_Return - Main Enter key - { - emit keyEnterPressed(); - return e->ignore(); - } - case Qt::Key_Down: //Qt::Key_Down - For both standalone and Numpad arrow down keys - emit keyDownPressed(); - break; - case Qt::Key_Escape: - emit keyEscapePressed(); - break; - default: - break; - } - return QLineEdit::keyPressEvent(e); -} diff --git a/BlockSettleUILib/ChatUI/ChatSearchLineEdit.h b/BlockSettleUILib/ChatUI/ChatSearchLineEdit.h deleted file mode 100644 index 25307839c..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchLineEdit.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHAT_SEARCH_LINE_EDIT_H -#define CHAT_SEARCH_LINE_EDIT_H - -#include -#include - -class ChatSearchLineEdit : - public QLineEdit -{ - Q_OBJECT -public: - ChatSearchLineEdit(QWidget *parent = nullptr); - ~ChatSearchLineEdit() override; - //void setActionsHandler(std::shared_ptr handler); - void setResetOnNextInput(bool value); -private: - void onTextChanged(const QString& text); -private: - //std::shared_ptr handler_; - bool resetOnNextInput_; - - // QWidget interface -protected: - void keyPressEvent(QKeyEvent *event) override; - -signals: - void keyDownPressed(); - void keyEnterPressed(); - void keyEscapePressed(); -}; - -#endif //CHAT_SEARCH_LINE_EDIT_H diff --git a/BlockSettleUILib/ChatUI/ChatSearchListVew.cpp b/BlockSettleUILib/ChatUI/ChatSearchListVew.cpp deleted file mode 100644 index 6247c0704..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchListVew.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatSearchListVew.h" - -#include - -ChatSearchListVew::ChatSearchListVew(QWidget *parent) : QTreeView(parent) -{ - setHeaderHidden(true); - setRootIsDecorated(false); - setSelectionMode(QAbstractItemView::SingleSelection); - setContextMenuPolicy(Qt::CustomContextMenu); -} - -void ChatSearchListVew::keyPressEvent(QKeyEvent *event) -{ - switch (event->key()) { - case Qt::Key_Escape: - emit leaveWithCloseRequired(); - break; - case Qt::Key_Up: - if (currentIndex().row() == 0) { - emit leaveRequired(); - } - break; - default: - break; - } - return QTreeView::keyPressEvent(event); -} diff --git a/BlockSettleUILib/ChatUI/ChatSearchListVew.h b/BlockSettleUILib/ChatUI/ChatSearchListVew.h deleted file mode 100644 index 300d161dc..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchListVew.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATSEARCHLISTVEW_H -#define CHATSEARCHLISTVEW_H - -#include - -class ChatSearchListVew : public QTreeView -{ - Q_OBJECT -public: - explicit ChatSearchListVew(QWidget *parent = nullptr); - -protected: - void keyPressEvent(QKeyEvent *event) override; - -signals: - void leaveRequired(); - void leaveWithCloseRequired(); -}; - -#endif // CHATSEARCHLISTVEW_H diff --git a/BlockSettleUILib/ChatUI/ChatSearchListViewItemStyle.cpp b/BlockSettleUILib/ChatUI/ChatSearchListViewItemStyle.cpp deleted file mode 100644 index 877270794..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchListViewItemStyle.cpp +++ /dev/null @@ -1,21 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatSearchListViewItemStyle.h" - -ChatSearchListViewItemStyle::ChatSearchListViewItemStyle(QWidget *parent) - : QWidget(parent) - , colorContactUnknown_(Qt::gray) - , colorContactAccepted_(Qt::cyan) - , colorContactIncoming_(Qt::darkYellow) - , colorContactOutgoing_(Qt::darkGreen) - , colorContactRejected_(Qt::red) -{ -} diff --git a/BlockSettleUILib/ChatUI/ChatSearchListViewItemStyle.h b/BlockSettleUILib/ChatUI/ChatSearchListViewItemStyle.h deleted file mode 100644 index c5e9b8e10..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchListViewItemStyle.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATSEARCHLISTVIEWITEMSTYLE_H -#define CHATSEARCHLISTVIEWITEMSTYLE_H - -#include - -class ChatSearchListViewItemStyle : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor color_contact_unknown MEMBER colorContactUnknown_) - Q_PROPERTY(QColor color_contact_accepted MEMBER colorContactAccepted_) - Q_PROPERTY(QColor color_contact_incoming MEMBER colorContactIncoming_) - Q_PROPERTY(QColor color_contact_outgoing MEMBER colorContactOutgoing_) - Q_PROPERTY(QColor color_contact_rejected MEMBER colorContactRejected_) - -public: - explicit ChatSearchListViewItemStyle(QWidget *parent = nullptr); - -private: - QColor colorContactUnknown_; - QColor colorContactAccepted_; - QColor colorContactIncoming_; - QColor colorContactOutgoing_; - QColor colorContactRejected_; - -}; - -#endif // CHATSEARCHLISTVIEWITEMSTYLE_H diff --git a/BlockSettleUILib/ChatUI/ChatSearchPopup.cpp b/BlockSettleUILib/ChatUI/ChatSearchPopup.cpp deleted file mode 100644 index 42a0c5e11..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchPopup.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatSearchPopup.h" -#include "ui_ChatSearchPopup.h" - -#include -#include -#include - -ChatSearchPopup::ChatSearchPopup(QWidget *parent) : - QWidget(parent), - userID_(), - ui_(new Ui::ChatSearchPopup) -{ - ui_->setupUi(this); - - ui_->chatSearchPopupLabel->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui_->chatSearchPopupLabel, &QLabel::customContextMenuRequested, this, &ChatSearchPopup::onShowMenu); - - searchPopupMenu_ = new QMenu(this); - userContactAction_ = searchPopupMenu_->addAction(QString()); -} - -ChatSearchPopup::~ChatSearchPopup() -{ - searchPopupMenu_->deleteLater(); - delete ui_; -} - -void ChatSearchPopup::setUserID(const QString &userID) -{ - userID_ = userID; - - if (userID_.isEmpty()) { - ui_->chatSearchPopupLabel->setText(tr("User not found")); - } - else { - ui_->chatSearchPopupLabel->setText(userID_); - } -} - -void ChatSearchPopup::setUserIsInContacts(const bool &isInContacts) -{ - isInContacts_ = isInContacts; -} - -void ChatSearchPopup::onShowMenu(const QPoint &pos) -{ - if (!userID_.isEmpty()) { - - userContactAction_->disconnect(); - - if (!isInContacts_) { - userContactAction_->setText(tr("Add to contacts")); - userContactAction_->setStatusTip(QObject::tr("Click to add user to contact list")); - connect(userContactAction_, &QAction::triggered, - [this](bool) { emit sendFriendRequest(ui_->chatSearchPopupLabel->text()); } - ); - } - else { - userContactAction_->setText(tr("Remove from contacts")); - userContactAction_->setStatusTip(QObject::tr("Click to remove user from contact list")); - connect(userContactAction_, &QAction::triggered, - [this](bool) { emit removeFriendRequest(ui_->chatSearchPopupLabel->text()); } - ); - } - - searchPopupMenu_->exec(mapToGlobal(pos)); - } -} - -void ChatSearchPopup::setCustomPosition(const QWidget *widget, const int &moveX, const int &moveY) -{ - QPoint newPos = widget->mapToGlobal(widget->rect().bottomLeft()); - newPos.setX(newPos.x() + moveX); - newPos.setY(newPos.y() + moveY); - this->move(newPos); -} diff --git a/BlockSettleUILib/ChatUI/ChatSearchPopup.h b/BlockSettleUILib/ChatUI/ChatSearchPopup.h deleted file mode 100644 index 8b4deeace..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchPopup.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATSEARCHPOPUP_H -#define CHATSEARCHPOPUP_H - -#include - -namespace Ui { -class ChatSearchPopup; -} - -class QMenu; - -class ChatSearchPopup : public QWidget -{ - Q_OBJECT - -public: - explicit ChatSearchPopup(QWidget *parent = nullptr); - ~ChatSearchPopup(); - - void setUserID(const QString &userID); - void setUserIsInContacts(const bool &isInContacts); - void setCustomPosition(const QWidget *widget, const int &moveX, const int &moveY); - -signals: - void sendFriendRequest(const QString &userID); - void removeFriendRequest(const QString &userID); - -private slots: - void onShowMenu(const QPoint &pos); - -private: - Ui::ChatSearchPopup *ui_; - QMenu *searchPopupMenu_; - QString userID_; - bool isInContacts_; - QAction *userContactAction_; -}; - -#endif // CHATSEARCHPOPUP_H diff --git a/BlockSettleUILib/ChatUI/ChatSearchPopup.ui b/BlockSettleUILib/ChatUI/ChatSearchPopup.ui deleted file mode 100644 index 1c169c484..000000000 --- a/BlockSettleUILib/ChatUI/ChatSearchPopup.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - - ChatSearchPopup - - - - 0 - 0 - 400 - 300 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 - - - 3 - - - 3 - - - 3 - - - 3 - - - - - 0 - - - - - - - - - - - - - - - - - - - - diff --git a/BlockSettleUILib/ChatUI/ChatUserListTreeView.cpp b/BlockSettleUILib/ChatUI/ChatUserListTreeView.cpp deleted file mode 100644 index 123def1b9..000000000 --- a/BlockSettleUILib/ChatUI/ChatUserListTreeView.cpp +++ /dev/null @@ -1,376 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include -#include -#include -#include "ChatUserListTreeView.h" -#include "ChatClientUsersViewItemDelegate.h" -#include "BSMessageBox.h" -#include "EditContactDialog.h" - -using namespace bs; - -namespace { - // Translation - const QString contextMenuRemoveUser = QObject::tr("Remove from contacts"); - const QString contextMenuEditUser = QObject::tr("Edit contact"); - const QString contextMenuAcceptRequest = QObject::tr("Accept friend request"); - const QString contextMenuDeclineRequest = QObject::tr("Decline friend request"); - - const QString dialogRemoveContact = QObject::tr("Remove contact"); - const QString dialogRemoveCCAsContact = QObject::tr("Remove %1 as a contact?"); - const QString dialogRemoveContactAreYouSure = QObject::tr("Are you sure you wish to remove this contact?"); - - const QString noChatAvailable = QObject::tr("NO CHAT AVAILABLE"); - const QString chatTemplateSuffix = QObject::tr(" CHAT"); - const QString outgoingPendingContactRequest = QObject::tr("OUTGOING PENDING CONTACT REQUEST "); - const QString incomingContactRequest = QObject::tr("INCOMING CONTACT REQUEST "); -} - -ChatUserListTreeView::ChatUserListTreeView(QWidget *parent) - : QTreeView (parent) -{ - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, &QAbstractItemView::customContextMenuRequested, this, &ChatUserListTreeView::onCustomContextMenu); - setItemDelegate(new ChatClientUsersViewItemDelegate({}, this)); - - connect(this, &QTreeView::clicked, this, &ChatUserListTreeView::onClicked); - connect(this, &QTreeView::doubleClicked, this, &ChatUserListTreeView::onDoubleClicked); -} - -void ChatUserListTreeView::setActiveChatLabel(QLabel *label) -{ - label_ = label; -} - -void ChatUserListTreeView::editContact(const QModelIndex& index) -{ - PartyTreeItem* item = internalPartyTreeItem(index); - if (nullptr == item) { - return; - } - - const Chat::ClientPartyPtr clientPartyPtr = item->data().value(); - if (nullptr == clientPartyPtr) { - return; - } - - if (clientPartyPtr->isPrivateStandard()) { - Chat::PartyRecipientPtr recipientPtr = clientPartyPtr->getSecondRecipient(currentUser()); - - if (nullptr == recipientPtr) - { - return; - } - - EditContactDialog dialog( - QString::fromStdString(clientPartyPtr->userHash()), - QString::fromStdString(clientPartyPtr->displayName()), - recipientPtr->publicKeyTime(), - QString::fromStdString(recipientPtr->publicKey().toHexStr()), - parentWidget()->window()); - if (dialog.exec() == QDialog::Accepted) { - - // do not allow display name to contains only whitespaces - std::string newDisplayName = dialog.displayName().toStdString(); - std::string::iterator it_first_nonspace = std::find_if(newDisplayName.begin(), newDisplayName.end() - , [l = std::locale{}](auto ch) { - return !std::isspace(ch); - }); - - if (it_first_nonspace != newDisplayName.end()) - { - emit setDisplayName(clientPartyPtr->id(), newDisplayName); - } - } - } -} - -void ChatUserListTreeView::onCustomContextMenu(const QPoint & point) -{ - QModelIndex index = indexAt(point); - emit partyClicked(index); - if (!index.isValid()) { - return; - } - - PartyTreeItem* item = internalPartyTreeItem(index); - if (nullptr == item) { - return; - } - - const Chat::ClientPartyPtr clientPartyPtr = item->data().value(); - if (nullptr == clientPartyPtr) { - return; - } - - if (!clientPartyPtr->isPrivate()) { - return; - } - - if (clientPartyPtr->isPrivateOTC()) - { - return; - } - - QMenu contextMenu; - if (Chat::PartyState::INITIALIZED == clientPartyPtr->partyState()) { - QAction* removeAction = contextMenu.addAction(contextMenuRemoveUser); - removeAction->setData(index); - connect(removeAction, &QAction::triggered, this, &ChatUserListTreeView::onRemoveFromContacts); - contextMenu.addAction(removeAction); - - QAction* editAction = contextMenu.addAction(contextMenuEditUser); - editAction->setData(index); - connect(editAction, &QAction::triggered, this, &ChatUserListTreeView::onEditContact); - contextMenu.addAction(editAction); - } - - if (Chat::PartyState::REQUESTED == clientPartyPtr->partyState()) { - if (clientPartyPtr->partyCreatorHash() != currentUser()) { - // receiver of party - QAction* acceptAction = contextMenu.addAction(contextMenuAcceptRequest); - acceptAction->setData(index); - connect(acceptAction, &QAction::triggered, this, &ChatUserListTreeView::onAcceptFriendRequest); - contextMenu.addAction(acceptAction); - - QAction* declineAction = contextMenu.addAction(contextMenuDeclineRequest); - declineAction->setData(index); - connect(declineAction, &QAction::triggered, this, &ChatUserListTreeView::onDeclineFriendRequest); - contextMenu.addAction(declineAction); - } - else { - // creator of party - QAction* removeAction = contextMenu.addAction(contextMenuRemoveUser); - removeAction->setData(index); - connect(removeAction, &QAction::triggered, this, &ChatUserListTreeView::onRemoveFromContacts); - contextMenu.addAction(removeAction); - } - } - - if (contextMenu.isEmpty()) { - return; - } - - contextMenu.exec(viewport()->mapToGlobal(point)); - //selectionModel()->clearSelection(); -} - -void ChatUserListTreeView::onExpandGlobalOTC() -{ - auto* proxyModel = static_cast(model()); - if (!proxyModel) { - return; - } - - const QModelIndex otcGlobalIndex = proxyModel->getOTCGlobalRoot(); - if (!otcGlobalIndex.isValid()) { - return; - } - - // Qt 5.13 -> change this two functions to expandRecursively - expand(otcGlobalIndex.child(0, 0)); - expand(otcGlobalIndex.child(1, 0)); -} - -PartyTreeItem* ChatUserListTreeView::internalPartyTreeItem(const QModelIndex& index) -{ - if (!index.isValid()) { - return nullptr; - } - - auto* proxyModel = static_cast(model()); - if (!proxyModel) { - return nullptr; - } - - return proxyModel->getInternalData(index); -} - -void ChatUserListTreeView::onClicked(const QModelIndex &index) -{ - emit partyClicked(index); - if (!index.isValid()) { - return; - } - - PartyTreeItem* item = internalPartyTreeItem(index); - - if (nullptr == item || UI::ElementType::Container != item->modelType()) { - return; - } - - if (item->data().canConvert() && item->data().toString() == ChatModelNames::ContainerTabGlobal) { - return; - } - - if (isExpanded(index)) { - collapse(index); - } - else { - expand(index); - } -} - -void ChatUserListTreeView::onDoubleClicked(const QModelIndex &index) -{ - emit partyClicked(index); - PartyTreeItem* item = internalPartyTreeItem(index); - - if (nullptr == item) { - return; - } - - if (item->modelType() == UI::ElementType::Party) { - editContact(index); - } -} - -void ChatUserListTreeView::updateDependUi(const QModelIndex& index) -{ - auto proxyModel = qobject_cast(index.model()); - QModelIndex currentIndex = proxyModel ? proxyModel->mapToSource(index) : index; - PartyTreeItem* item = static_cast(currentIndex.internalPointer()); - auto chatPartiesTreeModel = qobject_cast(currentIndex.model()); - - const Chat::ClientPartyPtr clientPartyPtr = item->data().value(); - - if (!chatPartiesTreeModel) { - label_->setText(noChatAvailable); - return; - } - - if (!clientPartyPtr) { - label_->setText(noChatAvailable); - return; - } - - if (!label_) { - return; - } - - const QString upperUserName = QString::fromStdString(clientPartyPtr->displayName()).toUpper(); - - if (clientPartyPtr->isGlobal()) { - label_->setText(upperUserName + chatTemplateSuffix); - } - - if (clientPartyPtr->isPrivateStandard()) { - if ((Chat::PartyState::UNINITIALIZED == clientPartyPtr->partyState()) - || (Chat::PartyState::REQUESTED == clientPartyPtr->partyState())) { - - if (clientPartyPtr->partyCreatorHash() == chatPartiesTreeModel->currentUser()) { - label_->setText(outgoingPendingContactRequest + upperUserName); - } - else { - label_->setText(incomingContactRequest + upperUserName); - } - } - else if (Chat::PartyState::INITIALIZED == clientPartyPtr->partyState()) { - label_->setText(upperUserName + chatTemplateSuffix); - } - } -} - -void ChatUserListTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - QTreeView::currentChanged(current, previous); - PartyTreeItem* item = internalPartyTreeItem(current); - - if (!item) { - return; - } - - if (item->modelType() == UI::ElementType::Party) { - updateDependUi(current); - } -} - -void ChatUserListTreeView::onEditContact() -{ - QAction* action = qobject_cast(sender()); - - if (nullptr == action) { - return; - } - - QModelIndex index = action->data().toModelIndex(); - - editContact(index); -} - -const Chat::ClientPartyPtr ChatUserListTreeView::clientPartyPtrFromAction(const QAction* action) -{ - if (nullptr == action) { - return nullptr; - } - - QModelIndex index = action->data().toModelIndex(); - - PartyTreeItem* item = internalPartyTreeItem(index); - if (nullptr == item) { - return nullptr; - } - - return item->data().value(); -} - -const std::string& ChatUserListTreeView::currentUser() const -{ - return static_cast(model())->currentUser(); -} - -void ChatUserListTreeView::onRemoveFromContacts() -{ - QAction* action = qobject_cast(sender()); - const Chat::ClientPartyPtr clientPartyPtr = clientPartyPtrFromAction(action); - - if (nullptr == clientPartyPtr) { - return; - } - - BSMessageBox confirmRemoveContact( - BSMessageBox::question, - dialogRemoveContact, - dialogRemoveCCAsContact.arg(QString::fromStdString(clientPartyPtr->displayName())), - dialogRemoveContactAreYouSure, parentWidget() - ); - - if (confirmRemoveContact.exec() != QDialog::Accepted) { - return; - } - - emit removeFromContacts(clientPartyPtr->id()); -} - -void ChatUserListTreeView::onAcceptFriendRequest() -{ - QAction* action = qobject_cast(sender()); - const Chat::ClientPartyPtr clientPartyPtr = clientPartyPtrFromAction(action); - - if (nullptr == clientPartyPtr) { - return; - } - - emit acceptFriendRequest(clientPartyPtr->id()); -} - -void ChatUserListTreeView::onDeclineFriendRequest() -{ - QAction* action = qobject_cast(sender()); - const Chat::ClientPartyPtr clientPartyPtr = clientPartyPtrFromAction(action); - - if (nullptr == clientPartyPtr) { - return; - } - - emit declineFriendRequest(clientPartyPtr->id()); -} diff --git a/BlockSettleUILib/ChatUI/ChatUserListTreeView.h b/BlockSettleUILib/ChatUI/ChatUserListTreeView.h deleted file mode 100644 index bef56573c..000000000 --- a/BlockSettleUILib/ChatUI/ChatUserListTreeView.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATCLIENTUSERVIEW_H -#define CHATCLIENTUSERVIEW_H - -#include -#include "ChatUsersViewItemStyle.h" -#include "ChatProtocol/ChatClientService.h" - -class QLabel; -class QMenu; - -class ChatUsersViewItemStyle; -class PartyTreeItem; -class ChatPartiesTreeModel; - -class ChatUserListTreeView : public QTreeView -{ - Q_OBJECT - -public: - ChatUserListTreeView(QWidget * parent = nullptr); - // #new_logic : this should leave in chat widget - void setActiveChatLabel(QLabel * label); - -public slots: - void onCustomContextMenu(const QPoint &); - void onExpandGlobalOTC(); - -signals: - void partyClicked(const QModelIndex& index); - void removeFromContacts(const std::string& partyId); - void acceptFriendRequest(const std::string& partyId); - void declineFriendRequest(const std::string& partyId); - void setDisplayName(const std::string& partyId, const std::string& contactName); - -protected slots: - void currentChanged(const QModelIndex& current, const QModelIndex& previous) override; - -private slots: - void onClicked(const QModelIndex &); - void onDoubleClicked(const QModelIndex &); - void onEditContact(); - void onRemoveFromContacts(); - void onAcceptFriendRequest(); - void onDeclineFriendRequest(); - -private: - PartyTreeItem* internalPartyTreeItem(const QModelIndex& index); - const Chat::ClientPartyPtr clientPartyPtrFromAction(const QAction* action); - const std::string& currentUser() const; - void editContact(const QModelIndex& index); - - // #new_logic : this should leave in chat widget - void updateDependUi(const QModelIndex& index); - - -private: - // #new_logic : this should leave in chat widget - QLabel * label_; -}; - -#endif // CHATCLIENTUSERVIEW_H diff --git a/BlockSettleUILib/ChatUI/ChatUsersViewItemStyle.cpp b/BlockSettleUILib/ChatUI/ChatUsersViewItemStyle.cpp deleted file mode 100644 index 84dafea58..000000000 --- a/BlockSettleUILib/ChatUI/ChatUsersViewItemStyle.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatUsersViewItemStyle.h" - -ChatUsersViewItemStyle::ChatUsersViewItemStyle(QWidget *parent) - : QWidget(parent) - , colorRoom_(Qt::white) - , colorUserOnline_(Qt::white) - , colorUserOffline_(Qt::gray) - , colorContactOnline_(Qt::white) - , colorContactOffline_(Qt::gray) - , colorContactIncoming_(Qt::darkYellow) - , colorContactOutgoing_(Qt::darkGreen) - , colorContactRejected_(Qt::darkRed) - , colorHighlightBackground_(Qt::cyan) -{ - -} - -QColor ChatUsersViewItemStyle::colorCategoryItem() const -{ - return colorCategoryItem_; -} - -QColor ChatUsersViewItemStyle::colorRoom() const -{ - return colorRoom_; -} - -QColor ChatUsersViewItemStyle::colorUserOnline() const -{ - return colorUserOnline_; -} - -QColor ChatUsersViewItemStyle::colorUserOffline() const -{ - return colorUserOffline_; -} - -QColor ChatUsersViewItemStyle::colorContactOnline() const -{ - return colorContactOnline_; -} - -QColor ChatUsersViewItemStyle::colorContactOffline() const -{ - return colorContactOffline_; -} - -QColor ChatUsersViewItemStyle::colorContactIncoming() const -{ - return colorContactIncoming_; -} - -QColor ChatUsersViewItemStyle::colorContactOutgoing() const -{ - return colorContactOutgoing_; -} - -QColor ChatUsersViewItemStyle::colorContactRejected() const -{ - return colorContactRejected_; -} - -QColor ChatUsersViewItemStyle::colorHighlightBackground() const -{ - return colorHighlightBackground_; -} - -void ChatUsersViewItemStyle::setColorCategoryItem(QColor colorCategoryItem) -{ - colorCategoryItem_ = colorCategoryItem; -} - -void ChatUsersViewItemStyle::setColorRoom(QColor colorRoom) -{ - colorRoom_ = colorRoom; -} - -void ChatUsersViewItemStyle::setColorUserOnline(QColor colorUserOnline) -{ - colorUserOnline_ = colorUserOnline; -} - -void ChatUsersViewItemStyle::setColorUserOffline(QColor colorUserOffline) -{ - colorUserOffline_ = colorUserOffline; -} - -void ChatUsersViewItemStyle::setColorContactOnline(QColor colorContactOnline) -{ - colorContactOnline_ = colorContactOnline; -} - -void ChatUsersViewItemStyle::setColorContactOffline(QColor colorContactOffline) -{ - colorContactOffline_ = colorContactOffline; -} - -void ChatUsersViewItemStyle::setColorContactIncoming(QColor colorContactIncoming) -{ - colorContactIncoming_ = colorContactIncoming; -} - -void ChatUsersViewItemStyle::setColorContactOutgoing(QColor colorContactOutgoing) -{ - colorContactOutgoing_ = colorContactOutgoing; -} - -void ChatUsersViewItemStyle::setColorContactRejected(QColor colorContactRejected) -{ - colorContactRejected_ = colorContactRejected; -} - -void ChatUsersViewItemStyle::setColorHighlightBackground(QColor colorHighlightBackground) -{ - colorHighlightBackground_ = colorHighlightBackground; -} diff --git a/BlockSettleUILib/ChatUI/ChatUsersViewItemStyle.h b/BlockSettleUILib/ChatUI/ChatUsersViewItemStyle.h deleted file mode 100644 index 629c6200f..000000000 --- a/BlockSettleUILib/ChatUI/ChatUsersViewItemStyle.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATUSERSVIEWITEMSTYLE_H -#define CHATUSERSVIEWITEMSTYLE_H - -#include - -// #UI_ChatParty : Names should be updated bellow as far as termins have been changed -class ChatUsersViewItemStyle : public QWidget -{ - Q_OBJECT - Q_PROPERTY(QColor color_category_item READ colorCategoryItem WRITE setColorCategoryItem) - Q_PROPERTY(QColor color_room READ colorRoom WRITE setColorRoom) - Q_PROPERTY(QColor color_user_online READ colorUserOnline WRITE setColorUserOnline) - Q_PROPERTY(QColor color_user_offline READ colorUserOffline WRITE setColorUserOffline) - Q_PROPERTY(QColor color_contact_online READ colorContactOnline WRITE setColorContactOnline) - Q_PROPERTY(QColor color_contact_offline READ colorContactOffline WRITE setColorContactOffline) - Q_PROPERTY(QColor color_contact_incoming READ colorContactIncoming WRITE setColorContactIncoming) - Q_PROPERTY(QColor color_contact_outgoing READ colorContactOutgoing WRITE setColorContactOutgoing) - Q_PROPERTY(QColor color_contact_rejected READ colorContactRejected WRITE setColorContactRejected) - Q_PROPERTY(QColor color_highlight_background READ colorHighlightBackground WRITE setColorHighlightBackground) -private: - QColor colorCategoryItem_; - QColor colorRoom_; - QColor colorUserOnline_; - QColor colorUserOffline_; - QColor colorContactOnline_; - QColor colorContactOffline_; - QColor colorContactIncoming_; - QColor colorContactOutgoing_; - QColor colorContactRejected_; - QColor colorHighlightBackground_; - -public: - explicit ChatUsersViewItemStyle(QWidget *parent = nullptr); - - QColor colorCategoryItem() const; - QColor colorRoom() const; - QColor colorUserOnline() const; - QColor colorUserOffline() const; - QColor colorContactOnline() const; - QColor colorContactOffline() const; - QColor colorContactIncoming() const; - QColor colorContactOutgoing() const; - QColor colorContactRejected() const; - QColor colorHighlightBackground() const; - -signals: - -public slots: - void setColorCategoryItem(QColor colorCategoryItem); - void setColorRoom(QColor colorRoom); - void setColorUserOnline(QColor colorUserOnline); - void setColorUserOffline(QColor colorUserOffline); - void setColorContactOnline(QColor colorContactOnline); - void setColorContactOffline(QColor colorContactOffline); - void setColorContactIncoming(QColor colorContactIncoming); - void setColorContactOutgoing(QColor colorContactOutgoing); - void setColorContactRejected(QColor colorContactRejected); - void setColorHighlightBackground(QColor colorHighlightBackground); -}; -#endif // CHATUSERSVIEWITEMSTYLE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidget.cpp b/BlockSettleUILib/ChatUI/ChatWidget.cpp deleted file mode 100644 index 2dd67652b..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidget.cpp +++ /dev/null @@ -1,735 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ChatWidget.h" - -#include - -#include -#include - -#include "BSChatInput.h" -#include "BSMessageBox.h" -#include "ImportKeyBox.h" -#include "ChatClientUsersViewItemDelegate.h" -#include "ChatWidgetStates/ChatWidgetStates.h" -#include "ChatOTCHelper.h" -#include "OtcUtils.h" -#include "OtcClient.h" -#include "OTCRequestViewModel.h" -#include "OTCShieldWidgets/OTCWindowsManager.h" -#include "AuthAddressManager.h" -#include "MDCallbacksQt.h" -#include "AssetManager.h" -#include "ui_ChatWidget.h" -#include "UtxoReservationManager.h" -#include "ApplicationSettings.h" -#include "chat.pb.h" - -using namespace bs; -using namespace bs::network; - -namespace -{ - const QString showHistoryButtonName = QObject::tr("Show History"); -} - -ChatWidget::ChatWidget(QWidget* parent) - : QWidget(parent), ui_(new Ui::ChatWidget) -{ - qRegisterMetaType>(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - - ui_->setupUi(this); - -#ifndef Q_OS_WIN - ui_->timeLabel->setMinimumSize(ui_->timeLabel->property("minimumSizeLinux").toSize()); -#endif - - ui_->textEditMessages->onSetColumnsWidth(ui_->timeLabel->minimumWidth(), - ui_->iconLabel->minimumWidth(), - ui_->userLabel->minimumWidth(), - ui_->messageLabel->minimumWidth()); - - //Init UI and other stuff - ui_->frameContactActions->setVisible(false); - - ui_->textEditMessages->viewport()->installEventFilter(this); - ui_->input_textEdit->viewport()->installEventFilter(this); - ui_->treeViewUsers->viewport()->installEventFilter(this); - - ui_->showHistoryButton->setText(showHistoryButtonName); - ui_->showHistoryButton->setVisible(false); - - otcWindowsManager_ = std::make_shared(); - auto* sWidget = ui_->stackedWidgetOTC; - for (int index = 0; index < sWidget->count(); ++index) { - auto* widget = qobject_cast(sWidget->widget(index)); - if (widget) { - widget->setChatOTCManager(otcWindowsManager_); - connect(this, &ChatWidget::chatRoomChanged, widget, &OTCWindowsAdapterBase::onChatRoomChanged); - connect(this, &ChatWidget::onAboutToHide, widget, &OTCWindowsAdapterBase::onParentAboutToHide); - } - } - connect(otcWindowsManager_.get(), &OTCWindowsManager::syncInterfaceRequired, this, &ChatWidget::onUpdateOTCShield); - - changeState(); //Initial state is LoggedOut -} - -ChatWidget::~ChatWidget() -{ - // Should be done explicitly, since destructor for state could make changes inside chatWidget - stateCurrent_.reset(); -} - -void ChatWidget::init(const std::shared_ptr& connectionManager - , bs::network::otc::Env env - , const Chat::ChatClientServicePtr& chatClientServicePtr - , const std::shared_ptr& loggerPtr - , const std::shared_ptr& walletsMgr - , const std::shared_ptr &authManager - , const std::shared_ptr& armory - , const std::shared_ptr& signContainer - , const std::shared_ptr &mdCallbacks - , const std::shared_ptr& assetManager - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr& applicationSettings) -{ - loggerPtr_ = loggerPtr; - - // OTC - otcHelper_ = new ChatOTCHelper(this); - otcHelper_->init(env, loggerPtr, walletsMgr, armory, signContainer, - authManager, utxoReservationManager, applicationSettings); - otcWindowsManager_->init(walletsMgr, authManager, mdCallbacks, assetManager - , armory, utxoReservationManager); - - chatClientServicePtr_ = chatClientServicePtr; - - installEventFilter(this); - - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::clientLoggedOutFromServer, this, &ChatWidget::onLogout, Qt::QueuedConnection); - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::partyModelChanged, this, &ChatWidget::onPartyModelChanged, Qt::QueuedConnection); - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::searchUserReply, ui_->searchWidget, &SearchWidget::onSearchUserReply); - connect(ui_->searchWidget, &SearchWidget::showUserRoom, this, &ChatWidget::onShowUserRoom); - connect(ui_->searchWidget, &SearchWidget::contactFriendRequest, this, &ChatWidget::onContactFriendRequest); - connect(ui_->searchWidget, &SearchWidget::emailHashRequested, this, &ChatWidget::emailHashRequested); - - chatPartiesTreeModel_ = std::make_shared(chatClientServicePtr_, otcHelper_->client()); - - const ChatPartiesSortProxyModelPtr charTreeSortModel = std::make_shared(chatPartiesTreeModel_); - ui_->treeViewUsers->setModel(charTreeSortModel.get()); - ui_->treeViewUsers->sortByColumn(0, Qt::AscendingOrder); - ui_->treeViewUsers->setSortingEnabled(true); - ui_->treeViewUsers->setItemDelegate(new ChatClientUsersViewItemDelegate(charTreeSortModel, this)); - ui_->treeViewUsers->setActiveChatLabel(ui_->labelActiveChat); - - ui_->searchWidget->init(chatClientServicePtr); - ui_->searchWidget->onClearLineEdit(); - ui_->searchWidget->onSetLineEditEnabled(false); - ui_->searchWidget->onSetListVisible(false); - - ui_->textEditMessages->onSwitchToChat("Global"); - - connect(ui_->showHistoryButton, &QPushButton::pressed, this, &ChatWidget::onRequestAllPrivateMessages); - - - ui_->widgetOTCShield->init(walletsMgr, authManager, applicationSettings); - connect(ui_->widgetOTCShield, &OTCShield::requestPrimaryWalletCreation, this, &ChatWidget::requestPrimaryWalletCreation); - - // connections - // User actions - connect(ui_->treeViewUsers, &ChatUserListTreeView::partyClicked, this, &ChatWidget::onUserListClicked); - connect(ui_->treeViewUsers, &ChatUserListTreeView::removeFromContacts, this, &ChatWidget::onRemovePartyRequest); - connect(ui_->treeViewUsers, &ChatUserListTreeView::acceptFriendRequest, this, &ChatWidget::onContactRequestAcceptClicked); - connect(ui_->treeViewUsers, &ChatUserListTreeView::declineFriendRequest, this, &ChatWidget::onContactRequestRejectClicked); - connect(ui_->treeViewUsers, &ChatUserListTreeView::setDisplayName, this, &ChatWidget::onSetDisplayName); - // This should be queued connection to make sure first view is updated - connect(chatPartiesTreeModel_.get(), &ChatPartiesTreeModel::restoreSelectedIndex, this, &ChatWidget::onActivateCurrentPartyId, Qt::QueuedConnection); - - connect(ui_->input_textEdit, &BSChatInput::sendMessage, this, &ChatWidget::onSendMessage); - connect(ui_->textEditMessages, &ChatMessagesTextEdit::messageRead, this, &ChatWidget::onMessageRead); - connect(ui_->textEditMessages, &ChatMessagesTextEdit::newPartyRequest, this, &ChatWidget::onNewPartyRequest); - connect(ui_->textEditMessages, &ChatMessagesTextEdit::removePartyRequest, this, &ChatWidget::onRemovePartyRequest); - connect(ui_->textEditMessages, &ChatMessagesTextEdit::switchPartyRequest, this, &ChatWidget::onActivatePartyId); - - //connect(chatClientServicePtr_.get(), &Chat::ChatClientService::clientLoggedInToServer, this, &ChatWidget::onLogin, Qt::QueuedConnection); - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::clientLoggedInToServer, this, &ChatWidget::onLogin); - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::clientLoggedOutFromServer, this, &ChatWidget::onLogout, Qt::QueuedConnection); - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::partyModelChanged, this, &ChatWidget::onPartyModelChanged, Qt::QueuedConnection); - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::privateMessagesHistoryCount, this, &ChatWidget::onPrivateMessagesHistoryCount, Qt::QueuedConnection); - - const Chat::ClientPartyModelPtr clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::messageArrived, this, &ChatWidget::onSendArrived, Qt::QueuedConnection); - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::clientPartyStatusChanged, this, &ChatWidget::onClientPartyStatusChanged, Qt::QueuedConnection); - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::messageStateChanged, this, &ChatWidget::onMessageStateChanged, Qt::QueuedConnection); - - // Connect all signal that influence on widget appearance - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::messageArrived, this, &ChatWidget::onRegisterNewChangingRefresh, Qt::QueuedConnection); - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::clientPartyStatusChanged, this, &ChatWidget::onRegisterNewChangingRefresh, Qt::QueuedConnection); - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::messageStateChanged, this, &ChatWidget::onRegisterNewChangingRefresh, Qt::QueuedConnection); - - // OTC - connect(clientPartyModelPtr.get(), &Chat::ClientPartyModel::otcPrivatePartyReady, this, &ChatWidget::onOtcPrivatePartyReady, Qt::QueuedConnection); - - ui_->textEditMessages->onSetClientPartyModel(clientPartyModelPtr); - - otcRequestViewModel_ = new OTCRequestViewModel(otcHelper_->client(), this); - ui_->treeViewOTCRequests->setModel(otcRequestViewModel_); - connect(ui_->treeViewOTCRequests->selectionModel(), &QItemSelectionModel::currentChanged, this, &ChatWidget::onOtcRequestCurrentChanged); - connect(chatPartiesTreeModel_.get(), &ChatPartiesTreeModel::restoreSelectedIndex, this, &ChatWidget::onActivateGlobalOTCTableRow, Qt::QueuedConnection); - - connect(otcHelper_->client(), &OtcClient::sendPbMessage, this, &ChatWidget::sendOtcPbMessage); - connect(otcHelper_->client(), &OtcClient::sendContactMessage, this, &ChatWidget::onSendOtcMessage); - connect(otcHelper_->client(), &OtcClient::sendPublicMessage, this, &ChatWidget::onSendOtcPublicMessage); - connect(otcHelper_->client(), &OtcClient::peerUpdated, this, &ChatWidget::onOtcUpdated); - connect(otcHelper_->client(), &OtcClient::publicUpdated, this, &ChatWidget::onOtcPublicUpdated); - connect(otcHelper_->client(), &OtcClient::publicUpdated, otcRequestViewModel_, &OTCRequestViewModel::onRequestsUpdated); - connect(otcHelper_->client(), &OtcClient::peerError, this, &ChatWidget::onOTCPeerError); - - - connect(ui_->widgetNegotiateRequest, &OTCNegotiationRequestWidget::requestCreated, this, &ChatWidget::onOtcRequestSubmit); - connect(ui_->widgetPullOwnOTCRequest, &PullOwnOTCRequestWidget::currentRequestPulled, this, &ChatWidget::onOtcPullOrRejectCurrent); - connect(ui_->widgetNegotiateResponse, &OTCNegotiationResponseWidget::responseAccepted, this, &ChatWidget::onOtcResponseAccept); - connect(ui_->widgetNegotiateResponse, &OTCNegotiationResponseWidget::responseUpdated, this, &ChatWidget::onOtcResponseUpdate); - connect(ui_->widgetNegotiateResponse, &OTCNegotiationResponseWidget::responseRejected, this, &ChatWidget::onOtcPullOrRejectCurrent); - connect(ui_->widgetCreateOTCRequest, &CreateOTCRequestWidget::requestCreated, this, &ChatWidget::onOtcQuoteRequestSubmit); - connect(ui_->widgetCreateOTCResponse, &CreateOTCResponseWidget::responseCreated, this, &ChatWidget::onOtcQuoteResponseSubmit); - - ui_->widgetCreateOTCRequest->init(env); -} - -otc::PeerPtr ChatWidget::currentPeer() const -{ - const auto chartProxyModel = dynamic_cast(ui_->treeViewUsers->model()); - PartyTreeItem* partyTreeItem = chartProxyModel->getInternalData(ui_->treeViewUsers->currentIndex()); - if (!partyTreeItem || partyTreeItem->modelType() == bs::UI::ElementType::Container) { - return nullptr; - } - - const auto clientPartyPtr = partyTreeItem->data().value(); - if (!clientPartyPtr || clientPartyPtr->isGlobalStandard()) { - return nullptr; - } - - if (clientPartyPtr->isGlobalOTC()) { - const auto ¤tIndex = ui_->treeViewOTCRequests->selectionModel()->currentIndex(); - if (!currentIndex.isValid() || currentIndex.row() < 0 || currentIndex.row() >= int(otcHelper_->client()->requests().size())) { - // Show by default own request (if available) - return otcHelper_->client()->ownRequest(); - } - - return otcHelper_->client()->requests().at(size_t(currentIndex.row())); - } - - return otcHelper_->client()->peer(clientPartyPtr->userHash(), partyTreeItem->peerType); -} - -void ChatWidget::setUserType(UserType userType) -{ - userType_ = userType; -} - -void acceptPartyRequest(const std::string& partyId) {} -void rejectPartyRequest(const std::string& partyId) {} -void sendPartyRequest(const std::string& partyId) {} -void removePartyRequest(const std::string& partyId) {} - -void ChatWidget::onContactRequestAcceptClicked(const std::string& partyId) const -{ - stateCurrent_->onAcceptPartyRequest(partyId); -} - -void ChatWidget::onContactRequestRejectClicked(const std::string& partyId) const -{ - stateCurrent_->onRejectPartyRequest(partyId); -} - -void ChatWidget::onContactRequestSendClicked(const std::string& partyId) const -{ - stateCurrent_->onSendPartyRequest(partyId); -} - -void ChatWidget::onContactRequestCancelClicked(const std::string& partyId) const -{ - stateCurrent_->onRemovePartyRequest(partyId); -} - -void ChatWidget::onNewPartyRequest(const std::string& userName, const std::string& initialMessage) const -{ - stateCurrent_->onNewPartyRequest(userName, initialMessage); -} - -void ChatWidget::onRemovePartyRequest(const std::string& partyId) const -{ - stateCurrent_->onRemovePartyRequest(partyId); -} - -void ChatWidget::onOtcUpdated(const otc::PeerPtr &peer) -{ - stateCurrent_->onOtcUpdated(peer); -} - -void ChatWidget::onOtcPublicUpdated() const -{ - stateCurrent_->onOtcPublicUpdated(); - ui_->treeViewUsers->onExpandGlobalOTC(); -} - -void ChatWidget::onOTCPeerError(const otc::PeerPtr &peer, bs::network::otc::PeerErrorType type, const std::string* errorMsg) -{ - stateCurrent_->onOTCPeerError(peer, type, errorMsg); -} - -void ChatWidget::onUpdateOTCShield() const -{ - stateCurrent_->onUpdateOTCShield(); -} - -void ChatWidget::onEmailHashReceived(const std::string &email, const std::string &hash) const -{ - ui_->searchWidget->onEmailHashReceived(email, hash); -} - -void ChatWidget::onPartyModelChanged() const -{ - stateCurrent_->onResetPartyModel(); - // #new_logic : save expanding state - ui_->treeViewUsers->expandAll(); -} - -void ChatWidget::onLogin() -{ - const auto clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - ownUserId_ = clientPartyModelPtr->ownUserName(); - otcHelper_->setCurrentUserId(ownUserId_); - - changeState(); - stateCurrent_->onResetPartyModel(); - ui_->treeViewUsers->expandAll(); - - onActivatePartyId(QString::fromLatin1(Chat::GlobalRoomName)); -} - -void ChatWidget::onLogout() -{ - ownUserId_.clear(); - changeState(); -} - -void ChatWidget::showEvent(QShowEvent* e) -{ - // refreshView - if (bNeedRefresh_) { - bNeedRefresh_ = false; - onActivatePartyId(QString::fromStdString(currentPartyId_)); - } -} - -void ChatWidget::hideEvent(QHideEvent* event) -{ - emit onAboutToHide(); - QWidget::hideEvent(event); -} - -bool ChatWidget::eventFilter(QObject* sender, QEvent* event) -{ - const auto fClearSelection = [](QTextEdit* widget, bool bForce = false) { - if (!widget->underMouse() || bForce) { - QTextCursor textCursor = widget->textCursor(); - textCursor.clearSelection(); - widget->setTextCursor(textCursor); - } - }; - - if ( QEvent::MouseButtonPress == event->type()) { - fClearSelection(ui_->textEditMessages); - fClearSelection(ui_->input_textEdit); - } - - if (QEvent::KeyPress == event->type()) { - auto*keyEvent = dynamic_cast(event); - - // handle ctrl+c (cmd+c on macOS) - if (keyEvent->modifiers().testFlag(Qt::ControlModifier)) { - if (Qt::Key_C == keyEvent->key()) { - if (ui_->textEditMessages->textCursor().hasSelection()) { - QApplication::clipboard()->setText(ui_->textEditMessages->getFormattedTextFromSelection()); - fClearSelection(ui_->textEditMessages, true); - return true; - } - } - } - if (keyEvent->matches(QKeySequence::SelectAll)) { - fClearSelection(ui_->textEditMessages); - } - } - - return QWidget::eventFilter(sender, event); -} - -void ChatWidget::onProcessOtcPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response) const -{ - stateCurrent_->onProcessOtcPbMessage(response); -} - -void ChatWidget::onSendOtcMessage(const std::string& contactId, const BinaryData& data) const -{ - auto* chatProxyModel = dynamic_cast(ui_->treeViewUsers->model()); - assert(chatProxyModel); - - const QModelIndex index = chatProxyModel->getProxyIndexById(currentPartyId_); - const auto party = chatProxyModel->getInternalData(index); - assert(party); - - bool const isOTCGlobalRoot = (chatProxyModel->getOTCGlobalRoot() == index); - - Chat::ClientPartyPtr clientPartyPtr = nullptr; - if (party->peerType == bs::network::otc::PeerType::Contact && !isOTCGlobalRoot) { - clientPartyPtr = chatClientServicePtr_->getClientPartyModelPtr()->getStandardPartyForUsers(ownUserId_, contactId); - } - else { - clientPartyPtr = chatClientServicePtr_->getClientPartyModelPtr()->getOtcPartyForUsers(ownUserId_, contactId); - } - - if (!clientPartyPtr) { - SPDLOG_LOGGER_ERROR(loggerPtr_, "can't find valid private party to send OTC message"); - return; - } - stateCurrent_->onSendOtcMessage(clientPartyPtr->id(), OtcUtils::serializeMessage(data)); -} - -void ChatWidget::onSendOtcPublicMessage(const BinaryData &data) const -{ - stateCurrent_->onSendOtcPublicMessage(OtcUtils::serializePublicMessage(data)); -} - -void ChatWidget::onNewChatMessageTrayNotificationClicked(const QString& partyId) -{ - onActivatePartyId(partyId); -} - -void ChatWidget::onSendMessage() const -{ - stateCurrent_->onSendMessage(); -} - -void ChatWidget::onMessageRead(const std::string& partyId, const std::string& messageId) const -{ - stateCurrent_->onMessageRead(partyId, messageId); -} - -void ChatWidget::onSendArrived(const Chat::MessagePtrList& messagePtrList) const -{ - stateCurrent_->onProcessMessageArrived(messagePtrList); - - const auto clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - for (const auto& message : messagePtrList) - { - const auto privatePartyPtr = clientPartyModelPtr->getPrivatePartyById(message->partyId()); - if (privatePartyPtr && privatePartyPtr->id() == currentPartyId_ && privatePartyPtr->isPrivateStandard()) - { - // request only for private standard parties - // and only once per loop - chatClientServicePtr_->RequestPrivateMessagesHistoryCount(privatePartyPtr->id()); - return; - } - } -} - -void ChatWidget::onClientPartyStatusChanged(const Chat::ClientPartyPtr& clientPartyPtr) const -{ - stateCurrent_->onChangePartyStatus(clientPartyPtr); -} - -void ChatWidget::onMessageStateChanged(const std::string& partyId, const std::string& message_id, const int party_message_state) const -{ - stateCurrent_->onChangeMessageState(partyId, message_id, party_message_state); -} - -void ChatWidget::onUserListClicked(const QModelIndex& index) -{ - if (!index.isValid()) { - return; - } - - auto* chartProxyModel = dynamic_cast(ui_->treeViewUsers->model()); - PartyTreeItem* partyTreeItem = chartProxyModel->getInternalData(index); - - if (partyTreeItem->modelType() == bs::UI::ElementType::Container) { - return; - } - - const auto clientPartyPtr = partyTreeItem->data().value(); - chatTransition(clientPartyPtr); -} - -void ChatWidget::chatTransition(const Chat::ClientPartyPtr& clientPartyPtr) -{ - ui_->showHistoryButton->setVisible(false); - - const auto transitionChange = [this, clientPartyPtr]() { - currentPartyId_ = clientPartyPtr->id(); - }; - - switch (clientPartyPtr->partyState()) - { - case Chat::PartyState::UNINITIALIZED: - changeState(transitionChange); - break; - case Chat::PartyState::REQUESTED: - if (clientPartyPtr->partyCreatorHash() == ownUserId_) { - changeState(transitionChange); - } - else { - changeState(transitionChange); - } - break; - case Chat::PartyState::INITIALIZED: - changeState(transitionChange); - if (clientPartyPtr->isPrivateStandard()) { - chatClientServicePtr_->RequestPrivateMessagesHistoryCount(clientPartyPtr->id()); - } - break; - default: - break; - } - - emit chatRoomChanged(); -} - -void ChatWidget::onActivatePartyId(const QString& partyId) -{ - auto* chartProxyModel = dynamic_cast(ui_->treeViewUsers->model()); - const QModelIndex partyProxyIndex = chartProxyModel->getProxyIndexById(partyId.toStdString()); - if (!partyProxyIndex.isValid()) { - if (ownUserId_.empty()) { - currentPartyId_.clear(); - changeState(); - } - else { - Q_ASSERT(partyId != QString::fromLatin1(Chat::GlobalRoomName)); - onActivatePartyId(QString::fromLatin1(Chat::GlobalRoomName)); - } - return; - } - - ui_->treeViewUsers->setCurrentIndex(partyProxyIndex); - onUserListClicked(partyProxyIndex); -} - -void ChatWidget::onActivateGlobalPartyId() -{ - onActivatePartyId(QString::fromLatin1(Chat::GlobalRoomName)); -} - -void ChatWidget::onActivateCurrentPartyId() -{ - if (currentPartyId_.empty()) { - return; - } - - auto* chatProxyModel = dynamic_cast(ui_->treeViewUsers->model()); - Q_ASSERT(chatProxyModel); - - const QModelIndex index = chatProxyModel->getProxyIndexById(currentPartyId_); - if (!index.isValid()) { - onActivateGlobalPartyId(); - } - - if (ui_->treeViewUsers->selectionModel()->currentIndex() != index) { - ui_->treeViewUsers->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select); - } -} - -void ChatWidget::onActivateGlobalOTCTableRow() const -{ - const QDateTime timeStamp = otcHelper_->selectedGlobalOTCEntryTimeStamp(); - - if (!timeStamp.isValid()) { - return; - } - - const QModelIndex currentRequest = otcRequestViewModel_->getIndexByTimestamp(timeStamp); - - if (!currentRequest.isValid()) { - return; - } - - ui_->treeViewOTCRequests->setCurrentIndex(currentRequest); - stateCurrent_->onUpdateOTCShield(); -} - -void ChatWidget::onRegisterNewChangingRefresh() -{ - if (!isVisible() || !isActiveWindow()) { - bNeedRefresh_ = true; - } -} - -void ChatWidget::onShowUserRoom(const QString& userHash) -{ - const auto clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - - const Chat::ClientPartyPtr clientPartyPtr = clientPartyModelPtr->getStandardPartyForUsers(ownUserId_, userHash.toStdString()); - - if (!clientPartyPtr) { - return; - } - - chatTransition(clientPartyPtr); -} - -void ChatWidget::onContactFriendRequest(const QString& userHash) const -{ - ui_->textEditMessages->onShowRequestPartyBox(userHash.toStdString()); -} - -void ChatWidget::onSetDisplayName(const std::string& partyId, const std::string& contactName) const -{ - stateCurrent_->onUpdateDisplayName(partyId, contactName); -} - -void ChatWidget::onOtcRequestSubmit() const -{ - stateCurrent_->onOtcRequestSubmit(); -} - -void ChatWidget::onOtcResponseAccept() const -{ - stateCurrent_->onOtcResponseAccept(); -} - -void ChatWidget::onOtcResponseUpdate() const -{ - stateCurrent_->onOtcResponseUpdate(); -} - -void ChatWidget::onOtcQuoteRequestSubmit() const -{ - stateCurrent_->onOtcQuoteRequestSubmit(); -} - -void ChatWidget::onOtcQuoteResponseSubmit() const -{ - stateCurrent_->onOtcQuoteResponseSubmit(); -} - -void ChatWidget::onOtcPullOrRejectCurrent() const -{ - stateCurrent_->onOtcPullOrRejectCurrent(); -} - -void ChatWidget::onUserPublicKeyChanged(const Chat::UserPublicKeyInfoList& userPublicKeyInfoList) -{ - // only one key needs to be replaced - show one message box - if (userPublicKeyInfoList.size() == 1) - { - onConfirmContactNewKeyData(userPublicKeyInfoList, false); - return; - } - - // multiple keys replacing - const QString detailsPattern = tr("Contacts Require key update: %1"); - - const QString detailsString = detailsPattern.arg(userPublicKeyInfoList.size()); - - BSMessageBox bsMessageBox(BSMessageBox::question, tr("Contacts Information Update"), - tr("Do you wish to import your full Contact list?"), - tr("Press OK to Import all Contact ID keys. Selecting Cancel will allow you to determine each contact individually."), - detailsString); - const int ret = bsMessageBox.exec(); - - onConfirmContactNewKeyData(userPublicKeyInfoList, QDialog::Accepted == ret); -} - -void ChatWidget::onConfirmContactNewKeyData(const Chat::UserPublicKeyInfoList& userPublicKeyInfoList, bool bForceUpdateAllUsers) -{ - Chat::UserPublicKeyInfoList acceptList; - Chat::UserPublicKeyInfoList declineList; - - for (const auto& userPkPtr : userPublicKeyInfoList) - { - if (userPkPtr->newPublicKey() == userPkPtr->oldPublicKey()) { - continue; - } - - if (bForceUpdateAllUsers) - { - acceptList.push_back(userPkPtr); - continue; - } - - ImportKeyBox box(BSMessageBox::question, tr("Import Contact '%1' Public Key?").arg(userPkPtr->user_hash()), this); - box.setAddrPort(std::string()); - box.setNewKeyFromBinary(userPkPtr->newPublicKey()); - box.setOldKeyFromBinary(userPkPtr->oldPublicKey()); - box.setCancelVisible(true); - - if (box.exec() == QDialog::Accepted) - { - acceptList.push_back(userPkPtr); - continue; - } - - declineList.push_back(userPkPtr); - } - - if (!acceptList.empty()) - { - chatClientServicePtr_->AcceptNewPublicKeys(acceptList); - } - - if (!declineList.empty()) - { - chatClientServicePtr_->DeclineNewPublicKeys(declineList); - } -} - -void ChatWidget::onOtcRequestCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) const -{ - onOtcPublicUpdated(); - - QDateTime selectedPeerTimeStamp; - if (currentPartyId_ == Chat::OtcRoomName) { - if (current.isValid() && current.row() >= 0 && current.row() < int(otcHelper_->client()->requests().size())) { - selectedPeerTimeStamp = otcHelper_->client()->requests().at(size_t(current.row()))->request.timestamp; - } - } - - otcHelper_->setGlobalOTCEntryTimeStamp(selectedPeerTimeStamp); -} - -void ChatWidget::onOtcPrivatePartyReady(const Chat::ClientPartyPtr& clientPartyPtr) const -{ - stateCurrent_->onOtcPrivatePartyReady(clientPartyPtr); -} - -void ChatWidget::onPrivateMessagesHistoryCount(const std::string& partyId, quint64 count) const -{ - if (currentPartyId_ != partyId) { - return; - } - - if (ui_->textEditMessages->messagesCount(partyId) < count) - { - ui_->showHistoryButton->setVisible(true); - return; - } - - ui_->showHistoryButton->setVisible(false); -} - -void ChatWidget::onRequestAllPrivateMessages() const -{ - chatClientServicePtr_->RequestAllHistoryMessages(currentPartyId_); -} diff --git a/BlockSettleUILib/ChatUI/ChatWidget.h b/BlockSettleUILib/ChatUI/ChatWidget.h deleted file mode 100644 index af09a3802..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidget.h +++ /dev/null @@ -1,197 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHAT_WIDGET_H -#define CHAT_WIDGET_H - -#include -#include -#include - -#include "ChatWidgetStates/AbstractChatWidgetState.h" -#include "ChatProtocol/ChatClientService.h" -#include "ChatProtocol/ClientParty.h" -#include "OtcTypes.h" - -class QItemSelection; - -class ApplicationSettings; -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class ChatOTCHelper; -class ChatPartiesTreeModel; -class MDCallbacksQt; -class OTCRequestViewModel; -class OTCWindowsManager; -class WalletSignerContainer; - -namespace Ui { - class ChatWidget; -} - -namespace bs { - namespace network { - enum class UserType : int; - } - namespace sync { - class WalletsManager; - } - class UTXOReservationManager; -} - -namespace Blocksettle { - namespace Communication { - namespace ProxyTerminalPb { - class Response; - } - } -} - -class ChatWidget final : public QWidget -{ - Q_OBJECT - -public: - explicit ChatWidget(QWidget* parent = nullptr); - ~ChatWidget() override; - - void init(const std::shared_ptr& connectionManager - , bs::network::otc::Env env - , const Chat::ChatClientServicePtr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr& - ); - - bs::network::otc::PeerPtr currentPeer() const; - - void setUserType(bs::network::UserType userType); - -protected: - void showEvent(QShowEvent* e) override; - void hideEvent(QHideEvent* event) override; - bool eventFilter(QObject* sender, QEvent* event) override; - -public slots: - void onProcessOtcPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response) const; - void onSendOtcMessage(const std::string& contactId, const BinaryData& data) const; - void onSendOtcPublicMessage(const BinaryData& data) const; - - void onNewChatMessageTrayNotificationClicked(const QString& partyId); - void onUpdateOTCShield() const; - - void onEmailHashReceived(const std::string &email, const std::string &hash) const; - void onUserPublicKeyChanged(const Chat::UserPublicKeyInfoList& userPublicKeyInfoList); - -private slots: - void onPartyModelChanged() const; - void onLogin(); - void onLogout(); - void onSendMessage() const; - void onMessageRead(const std::string& partyId, const std::string& messageId) const; - void onSendArrived(const Chat::MessagePtrList& messagePtrList) const; - void onClientPartyStatusChanged(const Chat::ClientPartyPtr& clientPartyPtr) const; - void onMessageStateChanged(const std::string& partyId, const std::string& message_id, int party_message_state) const; - void onUserListClicked(const QModelIndex& index); - void onActivatePartyId(const QString& partyId); - void onActivateGlobalPartyId(); - void onActivateCurrentPartyId(); - void onActivateGlobalOTCTableRow() const; - void onRegisterNewChangingRefresh(); - void onShowUserRoom(const QString& userHash); - void onContactFriendRequest(const QString& userHash) const; - void onSetDisplayName(const std::string& partyId, const std::string& contactName) const; - void onConfirmContactNewKeyData(const Chat::UserPublicKeyInfoList& userPublicKeyInfoList, bool bForceUpdateAllUsers); - void onPrivateMessagesHistoryCount(const std::string& partyId, quint64 count) const; - - void onOtcRequestCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) const; - - void onContactRequestAcceptClicked(const std::string& partyId) const; - void onContactRequestRejectClicked(const std::string& partyId) const; - void onContactRequestSendClicked(const std::string& partyId) const; - void onContactRequestCancelClicked(const std::string& partyId) const; - - void onNewPartyRequest(const std::string& userName, const std::string& initialMessage) const; - void onRemovePartyRequest(const std::string& partyId) const; - - void onOtcUpdated(const bs::network::otc::PeerPtr &peer); - void onOtcPublicUpdated() const; - void onOTCPeerError(const bs::network::otc::PeerPtr &peer, bs::network::otc::PeerErrorType type, const std::string* errorMsg); - - void onOtcRequestSubmit() const; - void onOtcResponseAccept() const; - void onOtcResponseUpdate() const; - void onOtcQuoteRequestSubmit() const; - void onOtcQuoteResponseSubmit() const; - void onOtcPullOrRejectCurrent() const; - - void onOtcPrivatePartyReady(const Chat::ClientPartyPtr& clientPartyPtr) const; - - void onRequestAllPrivateMessages() const; - -signals: - // OTC - void sendOtcPbMessage(const std::string& data); - void chatRoomChanged(); - void requestPrimaryWalletCreation(); - void emailHashRequested(const std::string &email); - void onAboutToHide(); - -private: - friend class AbstractChatWidgetState; - friend class ChatLogOutState; - friend class IdleState; - friend class PrivatePartyInitState; - friend class PrivatePartyUninitState; - friend class PrivatePartyRequestedOutgoingState; - friend class PrivatePartyRequestedIncomingState; - - template ::value>::type> - void changeState(std::function&& transitionChanges = []() {}) - { - // Exit previous state - stateCurrent_.reset(); - - // Enter new state - transitionChanges(); - stateCurrent_ = std::make_unique(this); - stateCurrent_->applyState(); - } - -protected: - std::unique_ptr stateCurrent_; - -private: - void chatTransition(const Chat::ClientPartyPtr& clientPartyPtr); - - QScopedPointer ui_; - Chat::ChatClientServicePtr chatClientServicePtr_; - OTCRequestViewModel* otcRequestViewModel_ = nullptr; - QPointer otcHelper_{}; - std::shared_ptr loggerPtr_; - std::shared_ptr chatPartiesTreeModel_; - std::shared_ptr otcWindowsManager_{}; - - std::string ownUserId_; - std::string currentPartyId_; - QMap draftMessages_; - bool bNeedRefresh_ = false; - - bs::network::UserType userType_{}; -}; - -#endif // CHAT_WIDGET_H diff --git a/BlockSettleUILib/ChatUI/ChatWidget.ui b/BlockSettleUILib/ChatUI/ChatWidget.ui deleted file mode 100644 index d1235a706..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidget.ui +++ /dev/null @@ -1,802 +0,0 @@ - - - - ChatWidget - - - - 0 - 0 - 1006 - 666 - - - - - 0 - 0 - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 270 - 0 - - - - - 250 - 16777215 - - - - false - - - - 1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - Chat ID - - - - - - - Qt::Horizontal - - - - 100 - 20 - - - - - - - - - 0 - 0 - - - - offline - - - Qt::TextSelectableByMouse - - - false - - - - - - - - - - 0 - - - 2 - - - 2 - - - - - - 0 - 100 - - - - - - - - Qt::NoFocus - - - Qt::ScrollBarAlwaysOff - - - QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed - - - Qt::ElideMiddle - - - QAbstractItemView::ScrollPerPixel - - - 10 - - - true - - - false - - - false - - - true - - - true - - - false - - - - - - - - - - - - false - - - true - - - false - - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - - 8 - - - - CHAT # - - - Qt::AlignCenter - - - true - - - true - - - - - - - PushButton - - - - - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 1 - - - - - 0 - 1 - - - - - - - - - 0 - 0 - - - - - 0 - 25 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 8 - 20 - - - - - - - - - 0 - 0 - - - - - 101 - 0 - - - - Time - - - - 110 - 0 - - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - User - - - - - - - - 50 - 0 - - - - Message - - - - - - - - - - - 0 - 0 - - - - Qt::NoFocus - - - true - - - QTextEdit::AutoAll - - - true - - - false - - - true - - - false - - - true - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - ACCEPT/SEND - - - - - - - REJECT/CANCEL - - - - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 56 - - - - - 16777215 - 84 - - - - false - - - true - - - TYPE MESSAGE AND SEND - - - true - - - false - - - true - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::WheelFocus - - - true - - - QAbstractItemView::SingleSelection - - - 0 - - - false - - - false - - - false - - - true - - - true - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 270 - 0 - - - - - 270 - 16777215 - - - - false - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - - ChatMessagesTextEdit - QTextBrowser -
ChatUI/ChatMessagesTextEdit.h
-
- - OTCGlobalTable - QTreeView -
ChatUI/OTCGlobalTable.h
-
- - OTCShield - QWidget -
ChatUI/OTCShieldWidgets/OTCShield.h
-
- - CreateOTCResponseWidget - QWidget -
ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.h
- 1 -
- - OTCNegotiationResponseWidget - QWidget -
ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.h
- 1 -
- - OTCNegotiationRequestWidget - QWidget -
ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.h
- 1 -
- - PullOwnOTCRequestWidget - QWidget -
ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.h
- 1 -
- - SearchWidget - QWidget -
ChatUI/SearchWidget.h
- 1 -
- - CreateOTCRequestWidget - QWidget -
ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.h
- 1 -
- - ChatUserListTreeView - QTreeView -
ChatUI/ChatUserListTreeView.h
-
- - BSChatInput - QTextBrowser -
ChatUI/BSChatInput.h
-
-
- - -
diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/AbstractChatWidgetState.cpp b/BlockSettleUILib/ChatUI/ChatWidgetStates/AbstractChatWidgetState.cpp deleted file mode 100644 index 12fd1ab12..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/AbstractChatWidgetState.cpp +++ /dev/null @@ -1,494 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "AbstractChatWidgetState.h" - -#include "ChatUI/ChatWidget.h" -#include "ChatUI/ChatOTCHelper.h" -#include "ChatUI/ChatPartiesTreeModel.h" -#include "ChatUI/OTCRequestViewModel.h" -#include "NotificationCenter.h" -#include "OtcClient.h" -#include "OtcUtils.h" - -namespace { - const int kMaxMessageNotifLength = 20; - const auto kTimeoutErrorWith = QObject::tr("OTC negotiation with %1 timed out"); - const auto kCancelledErrorWith = QObject::tr("OTC negotiation with %1 was canceled"); - const auto kRejectedErrorWith = QObject::tr("OTC order was rejected: %1"); -} // namespace - -using namespace bs::network; - -AbstractChatWidgetState::AbstractChatWidgetState(ChatWidget* chat) - : chat_(chat) -{ -} - -void AbstractChatWidgetState::onSendMessage() -{ - if (!canSendMessage()) { - return; - } - - std::string messageText = chat_->ui_->input_textEdit->toPlainText().toStdString(); - if (messageText.empty()) { - return; - } - - chat_->chatClientServicePtr_->SendPartyMessage(chat_->currentPartyId_, messageText); - chat_->ui_->input_textEdit->clear(); -} - -void AbstractChatWidgetState::onProcessMessageArrived(const Chat::MessagePtrList& messagePtrList) -{ - if (!canReceiveMessage()) { - return; - } - - if (messagePtrList.empty()) { - return; - } - - // Update all UI elements - - // Tab notifier - for (const auto message : messagePtrList) - { - if (message->partyMessageState() == Chat::PartyMessageState::SENT && - chat_->ownUserId_ != message->senderHash()) { - - const auto clientPartyPtr = getParty(message->partyId()); - if (!clientPartyPtr) { - continue; - } - - const auto messageTitle = clientPartyPtr->displayName(); - auto messageText = message->messageText(); - const auto otcText = OtcUtils::toReadableString(QString::fromStdString(messageText)); - const auto isOtc = !otcText.isEmpty(); - - if (!isOtc && messageText.length() > kMaxMessageNotifLength) { - messageText = messageText.substr(0, kMaxMessageNotifLength) + "..."; - } - - bs::ui::NotifyMessage notifyMsg; - notifyMsg.append(QString::fromStdString(messageTitle)); - notifyMsg.append(isOtc ? otcText : QString::fromStdString(messageText)); - notifyMsg.append(QString::fromStdString(message->partyId())); - notifyMsg.append(isOtc); - - NotificationCenter::notify(bs::ui::NotifyType::UpdateUnreadMessage, notifyMsg); - // Update tree - if (message->partyId() != chat_->currentPartyId_) { - chat_->chatPartiesTreeModel_->onIncreaseUnseenCounter(message->partyId(), 1, isOtc); - } - } - } - - chat_->ui_->textEditMessages->onMessageUpdate(messagePtrList); - - if (canPerformOTCOperations()) { - chat_->otcHelper_->onMessageArrived(messagePtrList); - } -} - -void AbstractChatWidgetState::onChangePartyStatus(const Chat::ClientPartyPtr& clientPartyPtr) -{ - if (!canChangePartyStatus()) { - return; - } - - chat_->chatPartiesTreeModel_->onPartyStatusChanged(clientPartyPtr); - chat_->otcHelper_->onPartyStateChanged(clientPartyPtr); - onUpdateOTCShield(); -} - -void AbstractChatWidgetState::onResetPartyModel() -{ - if (!canResetPartyModel()) { - return; - } - - chat_->chatPartiesTreeModel_->onPartyModelChanged(); - chat_->onActivatePartyId(QString::fromStdString(chat_->currentPartyId_)); -} - -void AbstractChatWidgetState::onMessageRead(const std::string& partyId, const std::string& messageId) -{ - if (!canResetReadMessage()) { - return; - } - - chat_->chatClientServicePtr_->SetMessageSeen(partyId, messageId); -} - -void AbstractChatWidgetState::onChangeMessageState(const std::string& partyId, const std::string& message_id, const int party_message_state) -{ - if (!canChangeMessageState()) { - return; - } - - const Chat::MessagePtr message = chat_->ui_->textEditMessages->onMessageStatusChanged(partyId, message_id, party_message_state); - - // Update tree view if needed - if (static_cast(party_message_state) == Chat::PartyMessageState::SEEN - && message && message->senderHash() != chat_->ownUserId_) { - chat_->chatPartiesTreeModel_->onDecreaseUnseenCounter(partyId, 1); - } -} - -void AbstractChatWidgetState::onAcceptPartyRequest(const std::string& partyId) -{ - if (!canAcceptPartyRequest()) { - return; - } - - chat_->chatClientServicePtr_->AcceptPrivateParty(partyId); -} - -void AbstractChatWidgetState::onRejectPartyRequest(const std::string& partyId) -{ - if (!canRejectPartyRequest()) { - return; - } - - chat_->chatClientServicePtr_->RejectPrivateParty(partyId); -} - -void AbstractChatWidgetState::onSendPartyRequest(const std::string& partyId) -{ - if (!canSendPartyRequest()) { - return; - } - - chat_->chatClientServicePtr_->RequestPrivateParty(partyId); -} - -void AbstractChatWidgetState::onRemovePartyRequest(const std::string& partyId) -{ - if (!canRemovePartyRequest()) { - return; - } - - chat_->chatClientServicePtr_->DeletePrivateParty(partyId); -} - -void AbstractChatWidgetState::onNewPartyRequest(const std::string& partyName, const std::string& initialMessage) -{ - if (!canSendPartyRequest()) { - return; - } - - chat_->chatClientServicePtr_->RequestPrivateParty(partyName, initialMessage); -} - -void AbstractChatWidgetState::onUpdateDisplayName(const std::string& partyId, const std::string& contactName) -{ - if (!canUpdatePartyName()) { - return; - } - - Chat::ClientPartyModelPtr clientPartyModelPtr = chat_->chatClientServicePtr_->getClientPartyModelPtr(); - Chat::ClientPartyPtr clientPartyPtr = clientPartyModelPtr->getClientPartyById(partyId); - clientPartyPtr->setDisplayName(contactName); - - if (clientPartyPtr->isGlobalStandard() || (clientPartyPtr->isPrivateStandard() && chat_->currentPartyId_ == partyId)) { - chat_->ui_->textEditMessages->onUpdatePartyName(partyId); - } -} - -void AbstractChatWidgetState::onSendOtcMessage(const std::string &partyId, const std::string& data) -{ - if (canPerformOTCOperations()) { - chat_->chatClientServicePtr_->SendPartyMessage(partyId, data); - } -} - -void AbstractChatWidgetState::onSendOtcPublicMessage(const std::string &data) -{ - if (canPerformOTCOperations()) { - chat_->chatClientServicePtr_->SendPartyMessage(Chat::OtcRoomName, data); - } -} - -void AbstractChatWidgetState::onProcessOtcPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response) -{ - if (canReceiveOTCOperations()) { - chat_->otcHelper_->onProcessOtcPbMessage(response); - } -} - -void AbstractChatWidgetState::onOtcUpdated(const otc::PeerPtr &peer) -{ - if (canReceiveOTCOperations() && chat_->currentPeer() == peer) { - onUpdateOTCShield(); - } -} - -void AbstractChatWidgetState::onOtcPublicUpdated() -{ - if (!canReceiveOTCOperations()) { - return; - } - - onUpdateOTCShield(); - chat_->chatPartiesTreeModel_->onGlobalOTCChanged(); -} - -void AbstractChatWidgetState::onUpdateOTCShield() -{ - if (!canReceiveOTCOperations()) { - return; - } - - applyRoomsFrameChange(); -} - -void AbstractChatWidgetState::onOTCPeerError(const bs::network::otc::PeerPtr &peer, bs::network::otc::PeerErrorType type, const std::string* errorMsg) -{ - if (!canReceiveOTCOperations()) { - return; - } - - const Chat::ClientPartyPtr clientPartyPtr = getPartyByUserHash(peer->contactId); - if (!clientPartyPtr) { - return; - } - - QString MessageBody; - switch (type) - { - case bs::network::otc::PeerErrorType::Timeout: - MessageBody = kTimeoutErrorWith.arg(QString::fromStdString(clientPartyPtr->displayName())); - break; - case bs::network::otc::PeerErrorType::Canceled: - MessageBody = kCancelledErrorWith.arg(QString::fromStdString(clientPartyPtr->displayName())); - break; - case bs::network::otc::PeerErrorType::Rejected: - assert(errorMsg); - MessageBody = kRejectedErrorWith.arg(QString::fromStdString(*errorMsg)); - break; - default: - break; - } - bs::ui::NotifyMessage notifyMsg; - notifyMsg.append(MessageBody); - - NotificationCenter::notify(bs::ui::NotifyType::OTCOrderError, notifyMsg); -} - -void AbstractChatWidgetState::onOtcRequestSubmit() -{ - if (canPerformOTCOperations()) { - chat_->otcHelper_->onOtcRequestSubmit(chat_->currentPeer(), chat_->ui_->widgetNegotiateRequest->offer()); - } -} - -void AbstractChatWidgetState::onOtcResponseAccept() -{ - if (canPerformOTCOperations()) { - chat_->otcHelper_->onOtcResponseAccept(chat_->currentPeer(), chat_->ui_->widgetNegotiateResponse->offer()); - } -} - -void AbstractChatWidgetState::onOtcResponseUpdate() -{ - if (canPerformOTCOperations()) { - chat_->otcHelper_->onOtcResponseUpdate(chat_->currentPeer(), chat_->ui_->widgetNegotiateResponse->offer()); - } -} - -void AbstractChatWidgetState::onOtcQuoteRequestSubmit() -{ - if (canPerformOTCOperations()) { - chat_->otcHelper_->onOtcQuoteRequestSubmit(chat_->ui_->widgetCreateOTCRequest->request()); - } -} - -void AbstractChatWidgetState::onOtcQuoteResponseSubmit() -{ - if (canPerformOTCOperations()) { - chat_->chatClientServicePtr_->RequestPrivatePartyOTC(chat_->currentPeer()->contactId); - } -} - -void AbstractChatWidgetState::onOtcPrivatePartyReady(const Chat::ClientPartyPtr& clientPartyPtr) -{ - if (canPerformOTCOperations() && clientPartyPtr->isPrivateOTC()) { - Chat::PartyRecipientsPtrList recipients = clientPartyPtr->getRecipientsExceptMe(chat_->ownUserId_); - for (const auto& recipient : recipients) { - if (chat_->currentPeer() && recipient->userHash() == chat_->currentPeer()->contactId) { - // found user, send request - chat_->otcHelper_->onOtcQuoteResponseSubmit(chat_->currentPeer(), chat_->ui_->widgetCreateOTCResponse->response()); - } - } - } -} - -void AbstractChatWidgetState::onOtcPullOrRejectCurrent() -{ - if (canPerformOTCOperations()) { - auto peer = chat_->currentPeer(); - if (!peer) { - assert(false); - return; - } - - Chat::ClientPartyModelPtr clientPartyModelPtr = chat_->chatClientServicePtr_->getClientPartyModelPtr(); - Chat::ClientPartyPtr clientPartyPtr = clientPartyModelPtr->getOtcPartyForUsers(chat_->ownUserId_, peer->contactId); - if (clientPartyPtr) { - chat_->chatClientServicePtr_->DeletePrivateParty(clientPartyPtr->id()); - } - - chat_->ui_->treeViewOTCRequests->selectionModel()->clearCurrentIndex(); - chat_->otcHelper_->onOtcPullOrReject(peer); - } -} - -void AbstractChatWidgetState::saveDraftMessage() -{ - const auto draft = chat_->ui_->input_textEdit->toPlainText(); - - if (draft.isEmpty()) { - chat_->draftMessages_.remove(chat_->currentPartyId_); - } - else { - chat_->draftMessages_.insert(chat_->currentPartyId_, draft); - } -} - -void AbstractChatWidgetState::restoreDraftMessage() -{ - const auto iDraft = chat_->draftMessages_.find(chat_->currentPartyId_); - if (iDraft != chat_->draftMessages_.cend()) { - chat_->ui_->input_textEdit->setText(iDraft.value()); - auto cursor = chat_->ui_->input_textEdit->textCursor(); - cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor); - chat_->ui_->input_textEdit->setTextCursor(cursor); - } -} - -void AbstractChatWidgetState::updateOtc() -{ - if (!canPerformOTCOperations()) { - chat_->ui_->widgetOTCShield->showOtcAvailableToTradingParticipants(); - return; - } - - const bool globalRoom = chat_->currentPartyId_ == Chat::OtcRoomName; - const auto &peer = chat_->currentPeer(); - if (!peer && !globalRoom) { - chat_->ui_->widgetOTCShield->showContactIsOffline(); - return; - } - - if (chat_->ui_->widgetOTCShield->onRequestCheckWalletSettings()) { - return; - } - - if (!peer) { - // Must be in globalRoom if checks above hold - assert(globalRoom); - chat_->ui_->stackedWidgetOTC->setCurrentIndex(static_cast(OTCPages::OTCCreateRequestPage)); - return; - } - - auto* otcWidget = qobject_cast(chat_->ui_->stackedWidgetOTC->currentWidget()); - bs::UtxoReservationToken reservation; // let's carry on reservation between otc windows changed - if (otcWidget) { - reservation = otcWidget->releaseReservation(); - if (!reservation.isValid()) { - reservation = chat_->otcHelper_->client()->releaseReservation(peer); - } - } - - using bs::network::otc::State; - OTCPages pageNumber = OTCPages::OTCShield; - switch (peer->state) { - case State::Idle: - if (peer->type == otc::PeerType::Contact) { - pageNumber = OTCPages::OTCNegotiateRequestPage; - } else if (peer->isOwnRequest) { - chat_->ui_->widgetPullOwnOTCRequest->setRequest(peer->request); - pageNumber = OTCPages::OTCPullOwnOTCRequestPage; - } else if (peer->type == otc::PeerType::Request) { - chat_->ui_->widgetCreateOTCResponse->setRequest(peer->request); - pageNumber = OTCPages::OTCCreateResponsePage; - } - reservation.release(); - break; - case State::QuoteSent: - chat_->ui_->widgetPullOwnOTCRequest->setResponse(peer->response); - pageNumber = OTCPages::OTCPullOwnOTCRequestPage; - break; - case State::QuoteRecv: - pageNumber = OTCPages::OTCNegotiateRequestPage; - break; - case State::OfferSent: - chat_->ui_->widgetPullOwnOTCRequest->setOffer(peer->offer); - pageNumber = OTCPages::OTCPullOwnOTCRequestPage; - break; - case State::OfferRecv: - chat_->ui_->widgetNegotiateResponse->setOffer(peer->offer); - pageNumber = OTCPages::OTCNegotiateResponsePage; - break; - case State::SentPayinInfo: - case State::WaitPayinInfo: - chat_->ui_->widgetOTCShield->showOtcSetupTransaction(); - if (reservation.isValid()) { - chat_->otcHelper_->client()->setReservation(peer, std::move(reservation)); - } - return; - case State::WaitBuyerSign: - chat_->ui_->widgetPullOwnOTCRequest->setPendingBuyerSign(peer->offer); - pageNumber = OTCPages::OTCPullOwnOTCRequestPage; - break; - case State::WaitSellerSeal: - chat_->ui_->widgetPullOwnOTCRequest->setPendingSellerSign(peer->offer); - pageNumber = OTCPages::OTCPullOwnOTCRequestPage; - break; - case State::WaitVerification: - case State::WaitSellerSign: - chat_->ui_->widgetOTCShield->showOtcSetupTransaction(); - if (reservation.isValid()) { - chat_->otcHelper_->client()->setReservation(peer, std::move(reservation)); - } - return; - case State::Blacklisted: - chat_->ui_->widgetOTCShield->showContactIsOffline(); - reservation.release(); - return; - default: - assert(false && " Did you forget to handle new otc::State state? "); - break; - } - - otcWidget = qobject_cast(chat_->ui_->stackedWidgetOTC->widget(static_cast(pageNumber))); - if (otcWidget) { - otcWidget->setPeer(*peer); - otcWidget->setReservation(std::move(reservation)); - otcWidget->onAboutToApply(); - } - - chat_->ui_->stackedWidgetOTC->setCurrentIndex(static_cast(pageNumber)); -} - -Chat::ClientPartyPtr AbstractChatWidgetState::getParty(const std::string& partyId) const -{ - Chat::ClientPartyModelPtr partyModel = chat_->chatClientServicePtr_->getClientPartyModelPtr(); - return partyModel->getClientPartyById(partyId); -} - -Chat::ClientPartyPtr AbstractChatWidgetState::getPartyByUserHash(const std::string& userHash) const -{ - Chat::ClientPartyModelPtr partyModel = chat_->chatClientServicePtr_->getClientPartyModelPtr(); - return partyModel->getStandardPartyForUsers(chat_->ownUserId_, userHash); -} diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/AbstractChatWidgetState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/AbstractChatWidgetState.h deleted file mode 100644 index 9c4c3f048..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/AbstractChatWidgetState.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef ABSTRACTCHATWIDGETSTATE_H -#define ABSTRACTCHATWIDGETSTATE_H - -#include "ChatProtocol/Message.h" -#include "ChatProtocol/ClientParty.h" -#include "ui_ChatWidget.h" - -enum class OTCPages : int -{ - OTCShield = 0, - OTCCreateRequestPage, - OTCCreateResponsePage, - OTCPullOwnOTCRequestPage, - OTCNegotiateRequestPage, - OTCNegotiateResponsePage -}; - -namespace Blocksettle { - namespace Communication { - namespace ProxyTerminalPb { - class Response; - } - } -} - -enum class StackedMessages { - TextEditMessage = 0, - OTCTable = 1 -}; - -class ChatWidget; -class AbstractChatWidgetState -{ -public: - explicit AbstractChatWidgetState(ChatWidget* chat); - virtual ~AbstractChatWidgetState() = default; - - void applyState() { - applyUserFrameChange(); - applyChatFrameChange(); - applyRoomsFrameChange(); - applyPostChanged(); - } - -protected: - virtual void applyUserFrameChange() = 0; - virtual void applyChatFrameChange() = 0; - virtual void applyRoomsFrameChange() = 0; - virtual void applyPostChanged() = 0; - - // slots -public: - void onSendMessage(); - void onProcessMessageArrived(const Chat::MessagePtrList& messagePtrList); - void onChangePartyStatus(const Chat::ClientPartyPtr& clientPartyPtr); - void onResetPartyModel(); - void onMessageRead(const std::string& partyId, const std::string& messageId); - void onChangeMessageState(const std::string& partyId, const std::string& message_id, const int party_message_state); - void onAcceptPartyRequest(const std::string& partyId); - void onRejectPartyRequest(const std::string& partyId); - void onSendPartyRequest(const std::string& partyId); - void onRemovePartyRequest(const std::string& partyId); - void onNewPartyRequest(const std::string& partyName, const std::string& initialMessage); - void onUpdateDisplayName(const std::string& partyId, const std::string& contactName); - - - // OTC - void onSendOtcMessage(const std::string &partyId, const std::string& data); - void onSendOtcPublicMessage(const std::string& data); - void onProcessOtcPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response); - - void onOtcUpdated(const bs::network::otc::PeerPtr &peer); - void onOtcPublicUpdated(); - void onUpdateOTCShield(); - void onOTCPeerError(const bs::network::otc::PeerPtr &peer, bs::network::otc::PeerErrorType type, const std::string* errorMsg); - - void onOtcRequestSubmit(); - void onOtcResponseAccept(); - void onOtcResponseUpdate(); - - void onOtcQuoteRequestSubmit(); - void onOtcQuoteResponseSubmit(); - - void onOtcPullOrRejectCurrent(); - - void onOtcPrivatePartyReady(const Chat::ClientPartyPtr& clientPartyPtr); - -protected: - - virtual bool canSendMessage() const { return false; } - virtual bool canReceiveMessage() const { return true; } - virtual bool canChangePartyStatus() const { return true; } - virtual bool canResetPartyModel() const { return true; } - virtual bool canResetReadMessage() const { return true; } - virtual bool canChangeMessageState() const { return true; } - virtual bool canAcceptPartyRequest() const { return true; } - virtual bool canRejectPartyRequest() const { return true; } - virtual bool canSendPartyRequest() const { return true; } - virtual bool canRemovePartyRequest() const { return true; } - virtual bool canUpdatePartyName() const { return true; } - virtual bool canPerformOTCOperations() const { return false; } - virtual bool canReceiveOTCOperations() const { return true; } - - void saveDraftMessage(); - void restoreDraftMessage(); - - void updateOtc(); - - Chat::ClientPartyPtr getParty(const std::string& partyId) const; - Chat::ClientPartyPtr getPartyByUserHash(const std::string& userHash) const; - - ChatWidget* chat_; -}; - -#endif // ABSTRACTCHATWIDGETSTATE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/ChatLogOutState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/ChatLogOutState.h deleted file mode 100644 index d4d5999a1..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/ChatLogOutState.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATLOGOUTSTATE_H -#define CHATLOGOUTSTATE_H - -#include "AbstractChatWidgetState.h" -#include "ChatUI/ChatOTCHelper.h" - -class ChatLogOutState : public AbstractChatWidgetState { -public: - explicit ChatLogOutState(ChatWidget* chat) : AbstractChatWidgetState(chat) {} - ~ChatLogOutState() override = default; -protected: - void applyUserFrameChange() override { - auto* searchWidget = chat_->ui_->searchWidget; - searchWidget->onClearLineEdit(); - searchWidget->onSetLineEditEnabled(false); - - if (chat_->chatPartiesTreeModel_) { - chat_->chatPartiesTreeModel_->onCleanModel(); - } - - chat_->ui_->labelUserName->setText(QObject::tr("offline")); - chat_->ui_->labelUserName->setProperty("headerLabelActivated", {}); - chat_->ui_->labelUserName->setTextInteractionFlags(Qt::NoTextInteraction); - qApp->style()->unpolish(chat_->ui_->labelUserName); - qApp->style()->polish(chat_->ui_->labelUserName); - } - void applyChatFrameChange() override { - chat_->ui_->stackedWidgetMessages->setCurrentIndex(static_cast(StackedMessages::TextEditMessage)); - chat_->ui_->textEditMessages->onLogout(); - - chat_->ui_->frameContactActions->setVisible(false); - - chat_->ui_->input_textEdit->setText(QLatin1Literal("")); - chat_->ui_->input_textEdit->setVisible(false); - chat_->ui_->input_textEdit->setEnabled(false); - - chat_->draftMessages_.clear(); - } - void applyRoomsFrameChange() override { - if (chat_->otcHelper_) { - chat_->otcHelper_->onLogout(); - } - - chat_->ui_->widgetOTCShield->showLoginToAccessOTC(); - } - - void applyPostChanged() override {}; - - bool canReceiveMessage() const override { return false; } - bool canResetReadMessage() const override { return false; } - bool canResetPartyModel() const override { return false; } - bool canChangeMessageState() const override { return false; } - bool canAcceptPartyRequest() const override { return false; } - bool canRejectPartyRequest() const override { return false; } - bool canSendPartyRequest() const override { return false; } - bool canRemovePartyRequest() const override { return false; } - bool canUpdatePartyName() const override { return false; } - bool canReceiveOTCOperations() const override { return false; } - -}; - -#endif // CHATLOGOUTSTATE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/ChatWidgetStates.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/ChatWidgetStates.h deleted file mode 100644 index 0e2fb41e2..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/ChatWidgetStates.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef CHATWIDGETSTATES_H -#define CHATWIDGETSTATES_H - -#include "ChatLogOutState.h" -#include "IdleState.h" -#include "PrivatePartyInitState.h" -#include "PrivatePartyUninitState.h" -#include "PrivatePartyRequestedOutgoingState.h" -#include "PrivatePartyRequestedIncomingState.h" - -#endif // CHATWIDGETSTATES_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/IdleState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/IdleState.h deleted file mode 100644 index 61123cfae..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/IdleState.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef IDLESTATE_H -#define IDLESTATE_H - -#include "AbstractChatWidgetState.h" - -class IdleState : public AbstractChatWidgetState { -public: - explicit IdleState(ChatWidget* chat) : AbstractChatWidgetState(chat) {} - ~IdleState() override = default; -protected: - void applyUserFrameChange() override { - chat_->ui_->searchWidget->onSetLineEditEnabled(true); - chat_->ui_->textEditMessages->onSwitchToChat({}); - - chat_->ui_->labelUserName->setText(QString::fromStdString(chat_->ownUserId_)); - chat_->ui_->labelUserName->setProperty("headerLabelActivated", true); - chat_->ui_->labelUserName->setTextInteractionFlags(Qt::TextSelectableByMouse); - qApp->style()->unpolish(chat_->ui_->labelUserName); - qApp->style()->polish(chat_->ui_->labelUserName); - } - void applyChatFrameChange() override { - chat_->ui_->textEditMessages->onSetOwnUserId(chat_->ownUserId_); - chat_->ui_->textEditMessages->onSwitchToChat(chat_->currentPartyId_); - - chat_->ui_->frameContactActions->setVisible(false); - - chat_->ui_->input_textEdit->setText({}); - chat_->ui_->input_textEdit->setVisible(true); - chat_->ui_->input_textEdit->setEnabled(false); - } - void applyRoomsFrameChange() override {} - void applyPostChanged() override {}; -}; - -#endif // IDLESTATE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyInitState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyInitState.h deleted file mode 100644 index 89eb463cf..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyInitState.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef PRIVATEPARTYINITSTATE_H -#define PRIVATEPARTYINITSTATE_H - -#include "AbstractChatWidgetState.h" -#include "ChatUI/ChatWidget.h" -#include "OtcClient.h" -#include "ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.h" -#include "ui_ChatWidget.h" - -class PrivatePartyInitState : public AbstractChatWidgetState { -public: - explicit PrivatePartyInitState(ChatWidget* chat) : AbstractChatWidgetState(chat) {} - ~PrivatePartyInitState() override { - saveDraftMessage(); - - // leave per, so let's send ownership over reservation to otc client if any - auto* otcWidget = qobject_cast(chat_->ui_->stackedWidgetOTC->currentWidget()); - if (otcWidget) { - auto reservation = otcWidget->releaseReservation(); - const auto &peer = chat_->currentPeer(); - if (reservation.isValid() && peer) { - chat_->otcHelper_->client()->setReservation(peer, std::move(reservation)); - } - } - }; -protected: - void applyUserFrameChange() override {} - void applyChatFrameChange() override { - Chat::ClientPartyPtr clientPartyPtr = getParty(chat_->currentPartyId_); - - assert(clientPartyPtr); - if (clientPartyPtr->isGlobalOTC()) { - chat_->ui_->treeViewOTCRequests->selectionModel()->reset(); - chat_->ui_->stackedWidgetMessages->setCurrentIndex(static_cast(StackedMessages::OTCTable)); - chat_->ui_->showHistoryButton->setVisible(false); - return; - } - - chat_->ui_->stackedWidgetMessages->setCurrentIndex(static_cast(StackedMessages::TextEditMessage)); - chat_->ui_->textEditMessages->onSwitchToChat(chat_->currentPartyId_); - - chat_->ui_->frameContactActions->setVisible(false); - - chat_->ui_->input_textEdit->setText({}); - chat_->ui_->input_textEdit->setVisible(true); - chat_->ui_->input_textEdit->setEnabled(true); - chat_->ui_->input_textEdit->setFocus(); - - restoreDraftMessage(); - } - void applyRoomsFrameChange() override { - Chat::ClientPartyPtr clientPartyPtr = getParty(chat_->currentPartyId_); - - auto checkIsTradingParticipant = [&]() -> bool { - const auto userCelerType = chat_->userType_; - if (bs::network::UserType::Dealing != userCelerType - && bs::network::UserType::Trading != userCelerType) { - chat_->ui_->widgetOTCShield->showOtcAvailableToTradingParticipants(); - return false; - } - - return true; - }; - - if (!clientPartyPtr) { - updateOtc(); - return; - } - - if (clientPartyPtr->isGlobalOTC()) { - if (!checkIsTradingParticipant()) { - return; - } - } - else if (clientPartyPtr->isGlobal()) { - chat_->ui_->widgetOTCShield->showChatExplanation(); - return; - } - else if (clientPartyPtr->isPrivate()) { - if (!checkIsTradingParticipant()) { - return; - } - - if (clientPartyPtr->clientStatus() == Chat::ClientStatus::OFFLINE) { - chat_->ui_->widgetOTCShield->showContactIsOffline(); - return; - } - - Chat::PartyRecipientPtr recipientPtr = clientPartyPtr->getSecondRecipient(chat_->ownUserId_); - CelerClient::CelerUserType counterPartyCelerType = recipientPtr->celerType(); - if (counterPartyCelerType != bs::network::UserType::Dealing - && counterPartyCelerType != bs::network::UserType::Trading) { - chat_->ui_->widgetOTCShield->showCounterPartyIsntTradingParticipant(); - return; - } - // check other party - } - - updateOtc(); - } - void applyPostChanged() override { - // enter peer chat window, let's take ownership on reservation - auto* otcWidget = qobject_cast(chat_->ui_->stackedWidgetOTC->currentWidget()); - const auto &peer = chat_->currentPeer(); - if (otcWidget && peer) { - otcWidget->setReservation(chat_->otcHelper_->client()->releaseReservation(peer)); - } - }; - - bool canSendMessage() const override { return true; } - bool canPerformOTCOperations() const override { return true; } -}; - -#endif // PRIVATEPARTYINITSTATE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyRequestedIncomingState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyRequestedIncomingState.h deleted file mode 100644 index 88729ba21..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyRequestedIncomingState.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef PRIVATEPARTYREQUESTEDINCOMINGSTATE_H -#define PRIVATEPARTYREQUESTEDINCOMINGSTATE_H - -#include "AbstractChatWidgetState.h" - -namespace { - // translation - const QString buttonAcceptPartyText = QObject::tr("ACCEPT"); - const QString buttonRejectPartyText = QObject::tr("REJECT"); -} - - -class PrivatePartyRequestedIncomingState : public AbstractChatWidgetState { -public: - explicit PrivatePartyRequestedIncomingState(ChatWidget* chat) : AbstractChatWidgetState(chat) {} - ~PrivatePartyRequestedIncomingState() override = default; -protected: - void applyUserFrameChange() override {} - void applyChatFrameChange() override { - chat_->ui_->textEditMessages->onSwitchToChat(chat_->currentPartyId_); - - chat_->ui_->pushButton_AcceptSend->setText(buttonAcceptPartyText); - chat_->ui_->pushButton_AcceptSend->disconnect(); - QObject::connect(chat_->ui_->pushButton_AcceptSend, &QPushButton::clicked, chat_, [this]() { - chat_->onContactRequestAcceptClicked(chat_->currentPartyId_); - }); - - chat_->ui_->pushButton_RejectCancel->setText(buttonRejectPartyText); - chat_->ui_->pushButton_RejectCancel->disconnect(); - QObject::connect(chat_->ui_->pushButton_RejectCancel, &QPushButton::clicked, chat_, [this]() { - chat_->onContactRequestRejectClicked(chat_->currentPartyId_); - }); - - chat_->ui_->frameContactActions->setVisible(true); - - chat_->ui_->input_textEdit->setText({}); - chat_->ui_->input_textEdit->setVisible(true); - chat_->ui_->input_textEdit->setEnabled(false); - } - void applyRoomsFrameChange() override { - chat_->ui_->widgetOTCShield->showOtcAvailableOnlyForAcceptedContacts(); - } - void applyPostChanged() override {}; -}; - -#endif // PRIVATEPARTYREQUESTEDINCOMINGSTATE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyRequestedOutgoingState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyRequestedOutgoingState.h deleted file mode 100644 index e17d73eff..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyRequestedOutgoingState.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef PRIVATEPARTYREQUESTEDOUTGOINGSTATE_H -#define PRIVATEPARTYREQUESTEDOUTGOINGSTATE_H - -#include "AbstractChatWidgetState.h" - -class PrivatePartyRequestedOutgoingState : public AbstractChatWidgetState { -public: - explicit PrivatePartyRequestedOutgoingState(ChatWidget* chat) : AbstractChatWidgetState(chat) {} - ~PrivatePartyRequestedOutgoingState() override = default; -protected: - void applyUserFrameChange() override {} - void applyChatFrameChange() override { - chat_->ui_->textEditMessages->onSwitchToChat(chat_->currentPartyId_); - - chat_->ui_->frameContactActions->setVisible(false); - - chat_->ui_->input_textEdit->setText({}); - chat_->ui_->input_textEdit->setVisible(true); - chat_->ui_->input_textEdit->setEnabled(false); - } - void applyRoomsFrameChange() override { - chat_->ui_->widgetOTCShield->showShieldOtcAvailableOnceAccepted(); - } - void applyPostChanged() override {}; -}; - -#endif // PRIVATEPARTYREQUESTEDOUTGOINGSTATE_H diff --git a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyUninitState.h b/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyUninitState.h deleted file mode 100644 index b5d2de78b..000000000 --- a/BlockSettleUILib/ChatUI/ChatWidgetStates/PrivatePartyUninitState.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef PRIVATEPARTYUNINITSTATE_H -#define PRIVATEPARTYUNINITSTATE_H - -#include "AbstractChatWidgetState.h" - -namespace { - // translation - const QString buttonSentPartyText = QObject::tr("SEND"); - const QString buttonCancelPartyText = QObject::tr("CANCEL"); -} - - -class PrivatePartyUninitState : public AbstractChatWidgetState { -public: - explicit PrivatePartyUninitState(ChatWidget* chat) : AbstractChatWidgetState(chat) {} - ~PrivatePartyUninitState() override = default; -protected: - void applyUserFrameChange() override {} - void applyChatFrameChange() override { - chat_->ui_->textEditMessages->onSwitchToChat(chat_->currentPartyId_); - - chat_->ui_->pushButton_AcceptSend->setText(buttonSentPartyText); - chat_->ui_->pushButton_AcceptSend->disconnect(); - QObject::connect(chat_->ui_->pushButton_AcceptSend, &QPushButton::clicked, chat_, [this]() { - chat_->onContactRequestSendClicked(chat_->currentPartyId_); - }); - - chat_->ui_->pushButton_RejectCancel->setText(buttonCancelPartyText); - chat_->ui_->pushButton_RejectCancel->disconnect(); - QObject::connect(chat_->ui_->pushButton_RejectCancel, &QPushButton::clicked, chat_, [this]() { - chat_->onContactRequestCancelClicked(chat_->currentPartyId_); - }); - - chat_->ui_->frameContactActions->setVisible(true); - - chat_->ui_->input_textEdit->setText({}); - chat_->ui_->input_textEdit->setVisible(true); - chat_->ui_->input_textEdit->setEnabled(false); - } - void applyRoomsFrameChange() override { - chat_->ui_->widgetOTCShield->showOtcAvailableOnlyForAcceptedContacts(); - } - void applyPostChanged() override {}; -}; - -#endif // PRIVATEPARTYUNINITSTATE_H diff --git a/BlockSettleUILib/ChatUI/OTCGlobalTable.cpp b/BlockSettleUILib/ChatUI/OTCGlobalTable.cpp deleted file mode 100644 index 7c82268c7..000000000 --- a/BlockSettleUILib/ChatUI/OTCGlobalTable.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCGlobalTable.h" -#include "OTCRequestViewModel.h" -#include "OtcTypes.h" - -namespace { - const int kLeftOffset = 10; -} - -OTCGlobalTable::OTCGlobalTable(QWidget* parent) - : TreeViewWithEnterKey(parent) -{ - setItemDelegateForColumn( - static_cast(OTCRequestViewModel::Columns::Duration), new OTCRequestsProgressDelegate(this)); - setItemDelegateForColumn( - static_cast(OTCRequestViewModel::Columns::Security), new LeftOffsetDelegate(this)); -} - -void OTCGlobalTable::drawRow(QPainter* painter,const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - if (!index.isValid()) { - TreeViewWithEnterKey::drawRow(painter, option, index); - return; - } - - const auto quotesModel = static_cast(model()); - bool isOwnQuote = quotesModel->data(index, static_cast(CustomRoles::OwnQuote)).toBool(); - - QStyleOptionViewItem itemOption(option); - if (isOwnQuote) { - itemOption.palette.setColor(QPalette::Text, itemStyle_.colorUserOnline()); - } - - TreeViewWithEnterKey::drawRow(painter, itemOption, index); -} - -bool OTCRequestsProgressDelegate::isDrawProgressBar(const QModelIndex& index) const -{ - return true; -} - -int OTCRequestsProgressDelegate::maxValue(const QModelIndex& index) const -{ - return std::chrono::duration_cast( - bs::network::otc::publicRequestTimeout()).count(); -} - -int OTCRequestsProgressDelegate::currentValue(const QModelIndex& index) const -{ - QDateTime startTimeStamp = index.data(static_cast(CustomRoles::RequestTimeStamp)).toDateTime(); - QDateTime endTimeStamp = startTimeStamp.addSecs( - std::chrono::duration_cast( - bs::network::otc::publicRequestTimeout()).count()); - - return QDateTime::currentDateTime().secsTo(endTimeStamp); -} - -void LeftOffsetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& opt, const QModelIndex& index) const -{ - QStyleOptionViewItem changedOpt = opt; - changedOpt.rect.setLeft(changedOpt.rect.left() + kLeftOffset); - - QStyledItemDelegate::paint(painter, changedOpt, index); -} diff --git a/BlockSettleUILib/ChatUI/OTCGlobalTable.h b/BlockSettleUILib/ChatUI/OTCGlobalTable.h deleted file mode 100644 index b79030600..000000000 --- a/BlockSettleUILib/ChatUI/OTCGlobalTable.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTC_GLOBAL_TABLE_H__ -#define __OTC_GLOBAL_TABLE_H__ - -#include "ChatUI/ChatUsersViewItemStyle.h" -#include "TreeViewWithEnterKey.h" -#include "ProgressViewDelegateBase.h" - -class OTCGlobalTable : public TreeViewWithEnterKey -{ - Q_OBJECT -public: - explicit OTCGlobalTable(QWidget* parent = nullptr); - ~OTCGlobalTable() override = default; - -protected: - void drawRow(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - -private: - ChatUsersViewItemStyle itemStyle_; -}; - -class OTCRequestsProgressDelegate : public ProgressViewDelegateBase -{ -public: - explicit OTCRequestsProgressDelegate(QWidget* parent = nullptr) - : ProgressViewDelegateBase(parent) - {} - ~OTCRequestsProgressDelegate() override = default; -protected: - bool isDrawProgressBar(const QModelIndex& index) const override; - int maxValue(const QModelIndex& index) const override; - int currentValue(const QModelIndex& index) const override; -}; - -class LeftOffsetDelegate : public QStyledItemDelegate -{ -public: - explicit LeftOffsetDelegate(QWidget* parent = nullptr) - : QStyledItemDelegate(parent) - {} - ~LeftOffsetDelegate() override = default; - - void paint(QPainter* painter, const QStyleOptionViewItem& opt, - const QModelIndex& index) const override; -}; - -#endif // __OTC_GLOBAL_TABLE_H__ diff --git a/BlockSettleUILib/ChatUI/OTCRequestViewModel.cpp b/BlockSettleUILib/ChatUI/OTCRequestViewModel.cpp deleted file mode 100644 index 6d57cca6e..000000000 --- a/BlockSettleUILib/ChatUI/OTCRequestViewModel.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCRequestViewModel.h" - -#include "OtcClient.h" -#include "OtcTypes.h" - -using namespace bs::network; - -namespace { - - const int kUpdateTimerInterval = 500; - - QString side(bs::network::otc::Side requestSide, bool isOwnRequest) { - if (!isOwnRequest) { - requestSide = bs::network::otc::switchSide(requestSide); - } - - return QString::fromStdString(otc::toString(requestSide)); - } - -} // namespace - -OTCRequestViewModel::OTCRequestViewModel(OtcClient *otcClient, QObject* parent) - : QAbstractTableModel(parent) - , otcClient_(otcClient) -{ - connect(&updateDurationTimer_, &QTimer::timeout, this, &OTCRequestViewModel::onUpdateDuration); - - updateDurationTimer_.setInterval(kUpdateTimerInterval); - updateDurationTimer_.start(); -} - -int OTCRequestViewModel::rowCount(const QModelIndex &parent) const -{ - return int(request_.size()); -} - -int OTCRequestViewModel::columnCount(const QModelIndex &parent) const -{ - return int(Columns::Latest) + 1; -} - -QVariant OTCRequestViewModel::data(const QModelIndex &index, int role) const -{ - const auto &requestData = request_.at(size_t(index.row())); - const auto &request = requestData.request_; - const auto column = Columns(index.column()); - - switch (role) { - case Qt::TextAlignmentRole: - return { static_cast(Qt::AlignLeft | Qt::AlignVCenter) }; - - case Qt::DisplayRole: - switch (column) { - case Columns::Security: return QStringLiteral("EUR/XBT"); - case Columns::Type: return QStringLiteral("OTC"); - case Columns::Product: return QStringLiteral("XBT"); - case Columns::Side: return side(request.ourSide, requestData.isOwnRequest_); - case Columns::Quantity: return QString::fromStdString(otc::toString(request.rangeType)); - case Columns::Duration: return {}; // OTCRequestsProgressDelegate - } - assert(false); - return {}; - - case static_cast(CustomRoles::OwnQuote): - return { requestData.isOwnRequest_ }; - - case static_cast(CustomRoles::RequestTimeStamp) : - return { request.timestamp }; - - default: - return {}; - } -} - -QVariant OTCRequestViewModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { - switch (Columns(section)) { - case Columns::Security: return tr("Security"); - case Columns::Type: return tr("Type"); - case Columns::Product: return tr("Product"); - case Columns::Side: return tr("Side"); - case Columns::Quantity: return tr("Quantity"); - case Columns::Duration: return tr("Duration"); - } - assert(false); - return {}; - } - - return QVariant{}; -} - -QModelIndex OTCRequestViewModel::getIndexByTimestamp(QDateTime timeStamp) -{ - for (int iReq = 0; iReq < request_.size(); ++iReq) { - if (timeStamp == request_[iReq].request_.timestamp) { - return index(iReq, 0); - } - } - - return {}; -} - -void OTCRequestViewModel::onRequestsUpdated() -{ - beginResetModel(); - request_.clear(); - for (const auto &peer : otcClient_->requests()) { - request_.push_back({ peer->request, peer->isOwnRequest }); - } - endResetModel(); - emit restoreSelectedIndex(); -} - -void OTCRequestViewModel::onUpdateDuration() -{ - if (rowCount() == 0) { - return; - } - - const qint64 timeout = QDateTime::currentDateTime().toSecsSinceEpoch() - std::chrono::duration_cast( - bs::network::otc::publicRequestTimeout()).count(); - - auto it = std::remove_if(request_.begin(), request_.end(), [&](const OTCRequest& req) { - return req.request_.timestamp.toSecsSinceEpoch() < timeout; - }); - - if (it != request_.end()) { - const int startIndex = std::distance(request_.begin(), it); - beginRemoveRows({}, startIndex, request_.size() - 1); - request_.erase(it, request_.end()); - endRemoveRows(); - } - - emit dataChanged(index(0, static_cast(Columns::Duration)), - index(rowCount() - 1, static_cast(Columns::Duration)), { Qt::DisplayRole }); -} diff --git a/BlockSettleUILib/ChatUI/OTCRequestViewModel.h b/BlockSettleUILib/ChatUI/OTCRequestViewModel.h deleted file mode 100644 index 04d914010..000000000 --- a/BlockSettleUILib/ChatUI/OTCRequestViewModel.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTC_REQUEST_VIEW_MODEL_H__ -#define __OTC_REQUEST_VIEW_MODEL_H__ - -#include -#include - -#include "OtcTypes.h" - -class OtcClient; - -enum class CustomRoles -{ - OwnQuote = Qt::UserRole + 1, - RequestTimeStamp -}; - -class OTCRequestViewModel : public QAbstractTableModel -{ - Q_OBJECT - -public: - OTCRequestViewModel(OtcClient *otcClient, QObject* parent = nullptr); - ~OTCRequestViewModel() override = default; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - - QModelIndex getIndexByTimestamp(QDateTime timeStamp); - - enum class Columns - { - Security, - Type, - Product, - Side, - Quantity, - Duration, - - Latest = Duration, - }; - -public slots: - void onRequestsUpdated(); - -private slots: - void onUpdateDuration(); - -signals: - void restoreSelectedIndex(); - -private: - struct OTCRequest - { - bs::network::otc::QuoteRequest request_; - bool isOwnRequest_; - }; - std::vector request_; - - OtcClient *otcClient_{}; - QTimer updateDurationTimer_; - -}; - -#endif diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.cpp deleted file mode 100644 index 0eaf5908a..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "CreateOTCRequestWidget.h" - -#include "AssetManager.h" -#include "OtcTypes.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncWalletsManager.h" -#include "ui_CreateOTCRequestWidget.h" - -#include -#include - -using namespace bs::network; - -CreateOTCRequestWidget::CreateOTCRequestWidget(QWidget* parent) - : OTCWindowsAdapterBase{parent} - , ui_{new Ui::CreateOTCRequestWidget{}} -{ - ui_->setupUi(this); -} - -CreateOTCRequestWidget::~CreateOTCRequestWidget() = default; - -void CreateOTCRequestWidget::init(bs::network::otc::Env env) -{ - env_ = static_cast(env); - connect(ui_->pushButtonBuy, &QPushButton::clicked, this, &CreateOTCRequestWidget::onBuyClicked); - connect(ui_->pushButtonSell, &QPushButton::clicked, this, &CreateOTCRequestWidget::onSellClicked); - connect(ui_->pushButtonSubmit, &QPushButton::clicked, this, &CreateOTCRequestWidget::requestCreated); - connect(ui_->pushButtonNumCcy, &QPushButton::clicked, this, &CreateOTCRequestWidget::onNumCcySelected); - - onSellClicked(); -} - -otc::QuoteRequest CreateOTCRequestWidget::request() const -{ - bs::network::otc::QuoteRequest result; - result.rangeType = otc::RangeType(ui_->comboBoxRange->currentData().toInt()); - result.ourSide = ui_->pushButtonSell->isChecked() ? otc::Side::Sell : otc::Side::Buy; - return result; -} - -void CreateOTCRequestWidget::onSellClicked() -{ - ui_->pushButtonSell->setChecked(true); - ui_->pushButtonBuy->setChecked(false); - onUpdateBalances(); -} - -void CreateOTCRequestWidget::onBuyClicked() -{ - ui_->pushButtonSell->setChecked(false); - ui_->pushButtonBuy->setChecked(true); - - onUpdateBalances(); -} - -void CreateOTCRequestWidget::onNumCcySelected() -{ - ui_->pushButtonNumCcy->setChecked(true); - ui_->pushButtonDenomCcy->setChecked(false); -} - -void CreateOTCRequestWidget::onUpdateBalances() -{ - QString totalBalance; - if (ui_->pushButtonBuy->isChecked()) { - const auto totalAssetBalance = getAssetManager()->getBalance(buyProduct_.toStdString(), bs::UTXOReservationManager::kIncludeZcOtc, nullptr); - - totalBalance = tr("%1 %2") - .arg(UiUtils::displayCurrencyAmount(totalAssetBalance)) - .arg(buyProduct_); - updateXBTRange(false, totalAssetBalance); - } - else { - const auto totalXBTBalance = getWalletManager()->getTotalBalance(); - - totalBalance = tr("%1 %2") - .arg(UiUtils::displayAmount(totalXBTBalance)) - .arg(QString::fromStdString(bs::network::XbtCurrency)); - - updateXBTRange(true, totalXBTBalance); - } - - ui_->labelBalanceValue->setText(totalBalance); -} - -void CreateOTCRequestWidget::updateXBTRange(bool isSell, BTCNumericTypes::balance_type balance /*= 0.0*/) -{ - otc::RangeType selectedRangeType = static_cast(ui_->comboBoxRange->currentData().toInt()); - - ui_->comboBoxRange->clear(); - - auto env = static_cast(env_); - auto lowestRangeType = otc::firstRangeValue(env); - ui_->comboBoxRange->addItem(QString::fromStdString(otc::toString(lowestRangeType)), static_cast(lowestRangeType)); - - otc::Range lowestRange = otc::getRange(lowestRangeType); - - auto checkLowestPossible = [&](int64_t lowestPrice) -> bool { - return (isSell && lowestPrice > balance) || (!isSell && lowestPrice > balance / buyIndicativePrice_); - }; - - if (checkLowestPossible(lowestRange.lower)) { - ui_->comboBoxRange->setDisabled(true); - ui_->pushButtonSubmit->setDisabled(true); - return; - } - - ui_->comboBoxRange->setEnabled(true); - ui_->pushButtonSubmit->setEnabled(true); - - int selectedIndex = 0; - for (int i = static_cast(lowestRangeType) + 1; - i <= static_cast(otc::lastRangeValue(env)); ++i) { - - auto rangeType = otc::RangeType(i); - if (checkLowestPossible(otc::getRange(rangeType).lower)) { - break; - } - - ui_->comboBoxRange->addItem(QString::fromStdString(otc::toString(rangeType)), i); - - if (rangeType == selectedRangeType) { - selectedIndex = static_cast(rangeType) - static_cast(lowestRangeType); - } - } - - ui_->comboBoxRange->setCurrentIndex(selectedIndex); -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.h deleted file mode 100644 index 4a72d9832..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __CREATE_OTC_REQUEST_WIDGET_H__ -#define __CREATE_OTC_REQUEST_WIDGET_H__ - -#include -#include - -#include "OtcTypes.h" -#include "OTCWindowsAdapterBase.h" - -namespace Ui { - class CreateOTCRequestWidget; -}; - -class CreateOTCRequestWidget : public OTCWindowsAdapterBase -{ - Q_OBJECT - -public: - CreateOTCRequestWidget(QWidget* parent = nullptr); - ~CreateOTCRequestWidget() override; - - void init(bs::network::otc::Env env); - - bs::network::otc::QuoteRequest request() const; - -signals: - void requestCreated(); - -protected slots: - void onUpdateBalances() override; - -private slots: - void onSellClicked(); - void onBuyClicked(); - void onNumCcySelected(); - -private: - void updateXBTRange(bool isSell, BTCNumericTypes::balance_type balance = 0.0); - - std::unique_ptr ui_; - - int env_{}; -}; - -#endif // __CREATE_OTC_REQUEST_WIDGET_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.ui b/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.ui deleted file mode 100644 index f12024a04..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCRequestWidget.ui +++ /dev/null @@ -1,739 +0,0 @@ - - - - CreateOTCRequestWidget - - - - 0 - 0 - 460 - 724 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - QUOTE REQUEST - - - true - - - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - GENERAL - - - true - - - - 10 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - - - Product Group - - - - - - - Spot XBT - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - 5 - - - 0 - - - 0 - - - - - Security ID - - - - - - - XBT/EUR - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - OTC DETAILS - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 0 - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Product - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 60 - 30 - - - - - 60 - 30 - - - - XBT - - - true - - - true - - - true - - - - - - - false - - - - 60 - 30 - - - - - 60 - 30 - - - - EUR - - - false - - - true - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 1 - - - 0 - - - 0 - - - 0 - - - - - Side - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 60 - 30 - - - - - 60 - 30 - - - - Buy - - - true - - - true - - - - - - - - 60 - 30 - - - - - 60 - 30 - - - - Sell - - - true - - - true - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 3 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity Range: - - - - - - - Qt::Horizontal - - - - 139 - 20 - - - - - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 3 - - - 10 - - - 5 - - - 5 - - - 10 - - - - - Balance - - - - - - - font-weight: bold; - - - xxx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 407 - - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - Settlement Period: 300 sec - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 35 - - - - - - - SUBMIT OTC - - - - - - - - - - - diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.cpp deleted file mode 100644 index b20e76694..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "CreateOTCResponseWidget.h" - -#include "AssetManager.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncWalletsManager.h" -#include "ui_CreateOTCResponseWidget.h" - -using namespace bs::network; - -CreateOTCResponseWidget::CreateOTCResponseWidget(QWidget* parent) - : OTCWindowsAdapterBase{parent} - , ui_{new Ui::CreateOTCResponseWidget{}} -{ - ui_->setupUi(this); - - connect(ui_->pushButtonSubmit, &QPushButton::clicked, this, &CreateOTCResponseWidget::responseCreated); - connect(ui_->widgetAmountRange, &RangeWidget::upperValueChanged, this, &CreateOTCResponseWidget::updateAcceptButton); - connect(ui_->widgetPriceRange, &RangeWidget::upperValueChanged, this, &CreateOTCResponseWidget::updateAcceptButton); -} - -CreateOTCResponseWidget::~CreateOTCResponseWidget() = default; - -void CreateOTCResponseWidget::setRequest(const otc::QuoteRequest &request) -{ - // TODO: Use MD - ourSide_ = request.ourSide; - - double currentIndicativePrice = ourSide_ != bs::network::otc::Side::Sell ? sellIndicativePrice_ : buyIndicativePrice_; - int lowerBound = std::max(static_cast(std::floor((currentIndicativePrice - 1000) / 1000) * 1000), 0); - int upperBound = std::max(static_cast(std::ceil((currentIndicativePrice + 1000) / 1000) * 1000), 1000); - ui_->widgetPriceRange->SetRange(lowerBound, upperBound); - - ui_->sideValue->setText(QString::fromStdString(otc::toString(request.ourSide))); - - ui_->rangeValue->setText(QString::fromStdString(otc::toString(request.rangeType))); - - auto range = otc::getRange(request.rangeType); - ui_->widgetAmountRange->SetRange(int(range.lower), int(range.upper)); - ui_->widgetAmountRange->SetLowerValue(int(range.lower)); - ui_->widgetAmountRange->SetUpperValue(int(range.upper)); - - ui_->pushButtonPull->hide(); -} - -otc::QuoteResponse CreateOTCResponseWidget::response() const -{ - otc::QuoteResponse response; - response.ourSide = ourSide_; - response.amount.lower = ui_->widgetAmountRange->GetLowerValue(); - response.amount.upper = ui_->widgetAmountRange->GetUpperValue(); - response.price.lower = otc::toCents(ui_->widgetPriceRange->GetLowerValue()); - response.price.upper = otc::toCents(ui_->widgetPriceRange->GetUpperValue()); - return response; -} - -void CreateOTCResponseWidget::setPeer(const bs::network::otc::Peer &peer) -{ - ui_->sideValue->setText(getSide(ourSide_, peer.isOwnRequest)); -} - -void CreateOTCResponseWidget::onUpdateBalances() -{ - QString totalBalance = tr("%1 %2") - .arg(UiUtils::displayAmount(getWalletManager()->getTotalBalance())) - .arg(QString::fromStdString(bs::network::XbtCurrency)); - - ui_->labelBalanceValue->setText(totalBalance); - - updateAcceptButton(); -} - -void CreateOTCResponseWidget::updateAcceptButton() -{ - // We cannot offer zero as price - bool isEnabled = ui_->widgetAmountRange->GetUpperValue() != 0 && ui_->widgetPriceRange->GetUpperValue() != 0; - - const auto totalXBTBalance = getWalletManager()->getTotalBalance(); - const auto totalEurBalance = getAssetManager()->getBalance(buyProduct_.toStdString(), bs::UTXOReservationManager::kIncludeZcOtc, nullptr); - - switch (ourSide_) - { - case bs::network::otc::Side::Buy: { - if (ui_->widgetPriceRange->GetLowerValue() * ui_->widgetAmountRange->GetLowerValue() > totalEurBalance) { - isEnabled = false; - } - } - break; - case bs::network::otc::Side::Sell: { - if (ui_->widgetAmountRange->GetLowerValue() > totalXBTBalance) { - isEnabled = false; - } - } - break; - default: - break; - } - - ui_->pushButtonSubmit->setEnabled(isEnabled); -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.h deleted file mode 100644 index 16ba5da5c..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __CREATE_OTC_RESPONSE_WIDGET_H__ -#define __CREATE_OTC_RESPONSE_WIDGET_H__ - -#include - -#include "OTCWindowsAdapterBase.h" -#include "OtcTypes.h" - -namespace Ui { - class CreateOTCResponseWidget; -}; - -class CreateOTCResponseWidget : public OTCWindowsAdapterBase -{ - Q_OBJECT -public: - CreateOTCResponseWidget(QWidget* parent = nullptr); - ~CreateOTCResponseWidget() override; - - void setRequest(const bs::network::otc::QuoteRequest &request); - - bs::network::otc::QuoteResponse response() const; - void setPeer(const bs::network::otc::Peer &peer) override; - -protected slots: - void onUpdateBalances() override; - -private slots: - void updateAcceptButton(); - -signals: - void responseCreated(); - -private: - std::unique_ptr ui_; - - bs::network::otc::Side ourSide_{}; - QString buyProduct_{ QLatin1String("EUR") }; -}; - -#endif // __CREATE_OTC_RESPONSE_WIDGET_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.ui b/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.ui deleted file mode 100644 index f1e35c1ab..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/CreateOTCResponseWidget.ui +++ /dev/null @@ -1,581 +0,0 @@ - - - - CreateOTCResponseWidget - - - - 0 - 0 - 616 - 724 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - OTC RESPONSE - - - true - - - 5 - - - - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - GENERAL - - - true - - - - 10 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - - - Product Group - - - - - - - Spot XBT - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - 5 - - - 0 - - - 0 - - - - - Security ID - - - - - - - XBT/EUR - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Side - - - - - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity Range - - - - - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - OTC DETAILS - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 5 - - - - - - 0 - 0 - - - - - 3 - - - 10 - - - 5 - - - 5 - - - 10 - - - - - Balance - - - - - - - font-weight: bold; - - - xxx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Bid ( EUR ) - - - - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity ( XBT ) - - - - - - - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 252 - - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - Settlement Period: 300 sec - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 35 - - - - PULL - - - - - - - - 0 - 35 - - - - - - - SUBMIT - - - - - - - - - - - RangeWidget - QWidget -
RangeWidget.h
- 1 -
-
- - -
diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.cpp deleted file mode 100644 index 94b377671..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCNegotiationRequestWidget.h" - -#include "AssetManager.h" -#include "AuthAddressManager.h" -#include "BSMessageBox.h" -#include "OTCWindowsManager.h" -#include "OtcTypes.h" -#include "TradesUtils.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "ui_OTCNegotiationRequestWidget.h" - -#include -#include -#include - -using namespace bs::network; - -namespace { - double kQuantityXBTSimpleStepAmount = 0.001; - const QString paymentWallet = QObject::tr("Payment Wallet"); - const QString receivingWallet = QObject::tr("Receiving Wallet"); -} - -OTCNegotiationRequestWidget::OTCNegotiationRequestWidget(QWidget* parent) - : OTCWindowsAdapterBase{ parent } - , ui_{ new Ui::OTCNegotiationRequestWidget{} } -{ - ui_->setupUi(this); - - ui_->priceSpinBox->setAccelerated(true); - ui_->priceSpinBox->setAccelerated(true); - - connect(ui_->pushButtonBuy, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onBuyClicked); - connect(ui_->pushButtonBuy, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onUpdateBalances); - connect(ui_->pushButtonSell, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onSellClicked); - connect(ui_->pushButtonSell, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onUpdateBalances); - connect(ui_->pushButtonAcceptRequest, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onSubmited); - connect(ui_->toolButtonXBTInputs, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onShowXBTInputsClicked); - connect(this, &OTCWindowsAdapterBase::xbtInputsProcessed, this, &OTCNegotiationRequestWidget::onXbtInputsProcessed); - connect(ui_->comboBoxXBTWallets, QOverload::of(&QComboBox::currentIndexChanged), this, &OTCNegotiationRequestWidget::onCurrentWalletChanged); - connect(ui_->quantityMaxButton, &QPushButton::clicked, this, &OTCNegotiationRequestWidget::onMaxQuantityClicked); - - connect(ui_->priceSpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &OTCNegotiationRequestWidget::onChanged); - connect(ui_->quantitySpinBox, qOverload(&QDoubleSpinBox::valueChanged), this, &OTCNegotiationRequestWidget::onChanged); - connect(ui_->authenticationAddressComboBox, qOverload(&QComboBox::currentIndexChanged), this, &OTCNegotiationRequestWidget::onChanged); - - ui_->quantitySpinBox->setSingleStep(kQuantityXBTSimpleStepAmount); - - onSellClicked(); - onChanged(); -} - -OTCNegotiationRequestWidget::~OTCNegotiationRequestWidget() = default; - -bs::network::otc::Offer OTCNegotiationRequestWidget::offer() const -{ - bs::network::otc::Offer result; - const bool isSell = ui_->pushButtonSell->isChecked(); - result.ourSide = isSell ? bs::network::otc::Side::Sell : bs::network::otc::Side::Buy; - result.price = bs::network::otc::toCents(ui_->priceSpinBox->value()); - result.amount = bs::network::otc::btcToSat(ui_->quantitySpinBox->value()); - - result.hdWalletId = ui_->comboBoxXBTWallets->currentData(UiUtils::WalletIdRole).toString().toStdString(); - result.authAddress = ui_->authenticationAddressComboBox->currentText().toStdString(); - - if (!isSell && ui_->receivingAddressComboBox->currentIndex() != 0) { - result.recvAddress = ui_->receivingAddressComboBox->currentText().toStdString(); - } - - result.inputs = selectedUTXOs(); - - auto walletType = UiUtils::getSelectedWalletType(ui_->comboBoxXBTWallets); - if (walletType & UiUtils::WalletsTypes::HardwareSW) { - auto purpose = UiUtils::getHwWalletPurpose(walletType); - result.walletPurpose.reset(new bs::hd::Purpose(purpose)); - } - - return result; -} - -void OTCNegotiationRequestWidget::onAboutToApply() -{ - onUpdateIndicativePrice(); -} - -void OTCNegotiationRequestWidget::setPeer(const bs::network::otc::Peer &peer) -{ - const bool isContact = (peer.type == otc::PeerType::Contact); - - switch (peer.type) { - case otc::PeerType::Contact: { - // Reset side to sell by default for contacts - toggleSideButtons(/*isSell*/ true); - ui_->quantitySpinBox->setMinimum(0); - ui_->priceSpinBox->setMinimum(0); - ui_->quantitySpinBox->setMaximum(std::numeric_limits::max()); - ui_->priceSpinBox->setMaximum(std::numeric_limits::max()); - break; - } - case otc::PeerType::Request: { - toggleSideButtons(peer.request.ourSide == otc::Side::Sell); - ui_->labelQuantityValue->setText(QString::fromStdString(otc::toString(peer.request.rangeType))); - const auto range = otc::getRange(peer.request.rangeType); - ui_->quantitySpinBox->setMinimum(range.lower); - ui_->quantitySpinBox->setMaximum(range.upper); - break; - } - case otc::PeerType::Response: { - // For public OTC side is fixed, use it from original request details - toggleSideButtons(peer.response.ourSide == otc::Side::Sell); - ui_->labelQuantityValue->setText(getXBTRange(peer.response.amount)); - ui_->labelBidValue->setText(getCCRange(peer.response.price)); - ui_->quantitySpinBox->setMinimum(peer.response.amount.lower); - ui_->quantitySpinBox->setMaximum(peer.response.amount.upper); - ui_->priceSpinBox->setMinimum(bs::network::otc::fromCents(peer.response.price.lower)); - ui_->priceSpinBox->setMaximum(bs::network::otc::fromCents(peer.response.price.upper)); - break; - } - } - - ui_->pushButtonBuy->setEnabled(isContact); - ui_->pushButtonSell->setEnabled(isContact); - ui_->rangeQuantity->setVisible(!isContact); - ui_->rangeBid->setVisible(!isContact && peer.type == otc::PeerType::Response); - - setSelectedInputs(peer.offer.inputs); - onChanged(); -} - -void OTCNegotiationRequestWidget::onSyncInterface() -{ - int index = UiUtils::fillHDWalletsComboBox(ui_->comboBoxXBTWallets, getWalletManager(), UiUtils::WalletsTypes::All); - - const auto walletId = getWalletManager()->getDefaultSpendWalletId(); - UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWallets, walletId, UiUtils::WalletsTypes::All); - - onCurrentWalletChanged(); - - UiUtils::fillAuthAddressesComboBoxWithSubmitted(ui_->authenticationAddressComboBox, getAuthManager()); - ui_->widgetButtons->setEnabled(ui_->authenticationAddressComboBox->isEnabled()); -} - -void OTCNegotiationRequestWidget::onUpdateBalances() -{ - QString totalBalance; - // #new_logic : fix me when different products security will be available - if (ui_->pushButtonBuy->isChecked()) { - totalBalance = tr("%1 %2") - .arg(UiUtils::displayCurrencyAmount(getAssetManager()->getBalance(buyProduct_.toStdString(), bs::UTXOReservationManager::kIncludeZcOtc, nullptr))) - .arg(buyProduct_); - ui_->quantitySpinBox->setMaximum(std::numeric_limits::max()); - } - else { - double currentBalance = getXBTSpendableBalance(); - totalBalance = tr("%1 %2") - .arg(UiUtils::displayAmount(currentBalance)) - .arg(QString::fromStdString(bs::network::XbtCurrency)); - ui_->quantitySpinBox->setMaximum(currentBalance); - } - - ui_->labelBalanceValue->setText(totalBalance); - onChanged(); -} - -void OTCNegotiationRequestWidget::onSubmited() -{ - auto minXbtAmount = bs::tradeutils::minXbtAmount(getUtxoManager()->feeRatePb()); - if (ui_->quantitySpinBox->value() < minXbtAmount.GetValueBitcoin()) { - auto minAmountStr = UiUtils::displayQuantity(minXbtAmount.GetValueBitcoin(), bs::network::XbtCurrency); - BSMessageBox(BSMessageBox::critical, tr("OTC"), tr("Invalid amount"), - tr("Amount will not cover network fee.\nMinimum amount: %1").arg(minAmountStr), this).exec(); - return; - } - - if (ui_->pushButtonBuy->isChecked()) { - emit requestCreated(); - return; - } - - if (!selectedUTXO_.empty()) { - emit requestCreated(); - return; - } - - submitProposal(ui_->comboBoxXBTWallets, bs::XBTAmount(ui_->quantitySpinBox->value()), - [caller = QPointer(this)]() { - if (!caller) { - return; - } - caller->requestCreated(); - }); -} - -std::shared_ptr OTCNegotiationRequestWidget::getCurrentHDWallet() const -{ - return getCurrentHDWalletFromCombobox(ui_->comboBoxXBTWallets); -} - -BTCNumericTypes::balance_type OTCNegotiationRequestWidget::getXBTSpendableBalance() const -{ - return getXBTSpendableBalanceFromCombobox(ui_->comboBoxXBTWallets); -} - -void OTCNegotiationRequestWidget::keyPressEvent(QKeyEvent* event) -{ - OTCWindowsAdapterBase::keyPressEvent(event); - if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) - && ui_->pushButtonAcceptRequest->isEnabled()) { - onSubmited(); - } -} - -void OTCNegotiationRequestWidget::onXbtInputsProcessed() -{ - onUpdateBalances(); - ui_->toolButtonXBTInputs->setEnabled(true); -} - -void OTCNegotiationRequestWidget::onSellClicked() -{ - ui_->pushButtonSell->setChecked(true); - ui_->pushButtonBuy->setChecked(false); - ui_->toolButtonXBTInputs->setVisible(true); - ui_->receivingAddressComboBox->setVisible(false); - ui_->receivingAddressLabel->setVisible(false); - ui_->quantityMaxButton->setVisible(true); - ui_->quantitySpinBox->setValue(0.0); - ui_->labelWallet->setText(paymentWallet); - - onUpdateIndicativePrice(); -} - -void OTCNegotiationRequestWidget::onBuyClicked() -{ - ui_->pushButtonSell->setChecked(false); - ui_->pushButtonBuy->setChecked(true); - ui_->toolButtonXBTInputs->setVisible(false); - ui_->receivingAddressComboBox->setVisible(true); - ui_->receivingAddressLabel->setVisible(true); - ui_->quantityMaxButton->setVisible(false); - ui_->quantitySpinBox->setValue(0.0); - ui_->labelWallet->setText(receivingWallet); - - onUpdateIndicativePrice(); -} - -void OTCNegotiationRequestWidget::onShowXBTInputsClicked() -{ - ui_->toolButtonXBTInputs->setEnabled(false); - showXBTInputsClicked(ui_->comboBoxXBTWallets); -} - -void OTCNegotiationRequestWidget::onChanged() -{ - bool activateAcceptButton = ui_->priceSpinBox->value() > 0 - && ui_->quantitySpinBox->value() > 0 - && !ui_->authenticationAddressComboBox->currentText().isEmpty(); - - if (!activateAcceptButton) { - ui_->pushButtonAcceptRequest->setDisabled(true); - return; - } - - if (ui_->pushButtonBuy->isChecked()) { - ui_->quantitySpinBox->setMaximum( - getAssetManager()->getBalance(buyProduct_.toStdString(), bs::UTXOReservationManager::kIncludeZcOtc, nullptr) / ui_->priceSpinBox->value()); - } - - ui_->pushButtonAcceptRequest->setEnabled(true); -} - -void OTCNegotiationRequestWidget::onChatRoomChanged() -{ - clearSelectedInputs(); -} - -void OTCNegotiationRequestWidget::onParentAboutToHide() -{ - clearSelectedInputs(); -} - -void OTCNegotiationRequestWidget::onCurrentWalletChanged() -{ - auto recvHdWallet = getCurrentHDWallet(); - if (!recvHdWallet) { - return; - } - if (!recvHdWallet->canMixLeaves()) { - auto xbtGroup = recvHdWallet->getGroup(recvHdWallet->getXBTGroupType()); - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWallets); - UiUtils::fillRecvAddressesComboBox(ui_->receivingAddressComboBox, { xbtGroup->getLeaf(purpose) }); - } - else { - UiUtils::fillRecvAddressesComboBoxHDWallet(ui_->receivingAddressComboBox, recvHdWallet, true); - } - - clearSelectedInputs(); - onUpdateBalances(); -} - -void OTCNegotiationRequestWidget::toggleSideButtons(bool isSell) -{ - ui_->pushButtonSell->setChecked(isSell); - ui_->pushButtonBuy->setChecked(!isSell); - if (isSell) { - onSellClicked(); - } - else { - onBuyClicked(); - } -} - -void OTCNegotiationRequestWidget::onUpdateIndicativePrice() -{ - const double indicativePrice = ui_->pushButtonBuy->isChecked() ? buyIndicativePrice_ : sellIndicativePrice_; - ui_->priceSpinBox->setValue(indicativePrice); -} - -void OTCNegotiationRequestWidget::onMaxQuantityClicked() -{ - const auto hdWallet = getCurrentHDWalletFromCombobox(ui_->comboBoxXBTWallets); - if (!hdWallet) { - ui_->quantitySpinBox->setValue(0); - return; - } - - std::vector utxos = selectedUTXOs(); - if (utxos.empty()) { - if (!hdWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWallets); - utxos = getUtxoManager()->getAvailableXbtUTXOs(hdWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcOtc); - } - else { - utxos = getUtxoManager()->getAvailableXbtUTXOs(hdWallet->walletId(), bs::UTXOReservationManager::kIncludeZcOtc); - } - } - - auto feeCb = [this, parentWidget = QPointer(this), utxos = std::move(utxos)](float fee) { - QMetaObject::invokeMethod(qApp, [this, parentWidget, fee, utxos = std::move(utxos)]{ - if (!parentWidget) { - return; - } - float feePerByteArmory = ArmoryConnection::toFeePerByte(fee); - auto feePerByte = std::max(feePerByteArmory, getUtxoManager()->feeRatePb()); - uint64_t total = 0; - for (const auto &utxo : utxos) { - total += utxo.getValue(); - } - const uint64_t fee = bs::tradeutils::estimatePayinFeeWithoutChange(utxos, feePerByte); - const double spendableQuantity = std::max(0.0, (total - fee) / BTCNumericTypes::BalanceDivider); - ui_->quantitySpinBox->setValue(spendableQuantity); - }); - }; - otcManager_->getArmory()->estimateFee(bs::tradeutils::feeTargetBlockCount(), feeCb); -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.h deleted file mode 100644 index 73f2b083a..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTC_NEGOTIATION_REQUEST_WIDGET_H__ -#define __OTC_NEGOTIATION_REQUEST_WIDGET_H__ - -#include -#include -#include - -#include "OtcTypes.h" -#include "OTCWindowsAdapterBase.h" - -namespace Ui { - class OTCNegotiationRequestWidget; -}; - -namespace bs { - namespace sync { - namespace hd { - class Wallet; - } - } -} - -class OTCNegotiationRequestWidget : public OTCWindowsAdapterBase -{ -Q_OBJECT -Q_DISABLE_COPY(OTCNegotiationRequestWidget) - -public: - OTCNegotiationRequestWidget(QWidget* parent = nullptr); - ~OTCNegotiationRequestWidget() override; - - bs::network::otc::Offer offer() const; - - void setPeer(const bs::network::otc::Peer &peer) override; - -signals: - void requestCreated(); - -public slots: - void onAboutToApply() override; - void onChatRoomChanged() override; - void onParentAboutToHide() override; - -protected slots: - void onSyncInterface() override; - void onUpdateBalances() override; - - void onSubmited(); - -protected: - std::shared_ptr getCurrentHDWallet() const; - BTCNumericTypes::balance_type getXBTSpendableBalance() const; - - void keyPressEvent(QKeyEvent* event) override; - -private slots: - void onSellClicked(); - void onBuyClicked(); - void onShowXBTInputsClicked(); - void onXbtInputsProcessed(); - void onChanged(); - void onUpdateIndicativePrice(); - void onMaxQuantityClicked(); - void onCurrentWalletChanged(); - -private: - void toggleSideButtons(bool isSell); - - std::unique_ptr ui_; -}; - -#endif // __OTC_NEGOTIATION_REQUEST_WIDGET_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.ui b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.ui deleted file mode 100644 index 6dbf0405f..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationRequestWidget.ui +++ /dev/null @@ -1,971 +0,0 @@ - - - - OTCNegotiationRequestWidget - - - - 0 - 0 - 558 - 961 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - OTC REQUEST - - - true - - - - - - - - - - 5 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - GENERAL - - - true - - - - 10 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - - - Product Group - - - - - - - Spot XBT - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - 5 - - - 0 - - - 0 - - - - - Security ID - - - - - - - XBT/EUR - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity (XBT) - - - - - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Bid (EUR) - - - - - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - OTC ENTRY - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 5 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 0 - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Side - - - - - - - - 60 - 30 - - - - - 60 - 30 - - - - Buy - - - true - - - true - - - - - - - - 60 - 30 - - - - - 60 - 30 - - - - Sell - - - true - - - true - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 3 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - Price - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - 2 - - - 1.000000000000000 - - - 1000000.000000000000000 - - - 5.000000000000000 - - - 10000.000000000000000 - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - Quantity - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - 8 - - - 0.000000000000000 - - - 1000000.000000000000000 - - - 5.000000000000000 - - - 10000.000000000000000 - - - - - - - - 100 - 21 - - - - - 100 - 21 - - - - &Max - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 5 - - - 5 - - - 5 - - - 10 - - - - - Balance - - - - - - - font-weight: bold; - - - xxx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - SETTLEMENT DETAILS - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 0 - - - - - Receiving Wallet - - - - - - - 5 - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - 100 - 21 - - - - - 100 - 21 - - - - &Inputs - - - - - - - - - - - 0 - - - - - Receiving Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - 0 - - - - - Authentication Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 3 - - - - - - 0 - 35 - - - - - - - Submit - - - - - - - - - - - diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.cpp deleted file mode 100644 index 57c6403af..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCNegotiationResponseWidget.h" - -#include "AssetManager.h" -#include "AuthAddressManager.h" -#include "OtcClient.h" -#include "OtcTypes.h" -#include "TradesUtils.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "ui_OTCNegotiationResponseWidget.h" - -#include -#include - -namespace { - const QString paymentWallet = QObject::tr("Payment Wallet"); - const QString receivingWallet = QObject::tr("Receiving Wallet"); -} - -OTCNegotiationResponseWidget::OTCNegotiationResponseWidget(QWidget* parent) - : OTCWindowsAdapterBase{ parent } - , ui_{ new Ui::OTCNegotiationResponseWidget{} } -{ - ui_->setupUi(this); - - ui_->pushButtonCancel->setText(tr("Reject")); - - connect(ui_->offerSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged) - , this, &OTCNegotiationResponseWidget::onChanged); - connect(ui_->bidSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged) - , this, &OTCNegotiationResponseWidget::onChanged); - - connect(ui_->comboBoxXBTWallets, QOverload::of(&QComboBox::currentIndexChanged), this, &OTCNegotiationResponseWidget::onCurrentWalletChanged); - connect(ui_->pushButtonAccept, &QPushButton::clicked, this, &OTCNegotiationResponseWidget::onAcceptOrUpdateClicked); - connect(ui_->pushButtonCancel, &QPushButton::clicked, this, &OTCNegotiationResponseWidget::responseRejected); - connect(ui_->toolButtonXBTInputs, &QPushButton::clicked, this, &OTCNegotiationResponseWidget::onShowXBTInputsClicked); - connect(this, &OTCWindowsAdapterBase::xbtInputsProcessed, this, &OTCNegotiationResponseWidget::onXbtInputsProcessed); - - ui_->quantitySpinBox->setEnabled(false); - - timeoutSec_ = getSeconds(bs::network::otc::negotiationTimeout()); - - onChanged(); -} - -OTCNegotiationResponseWidget::~OTCNegotiationResponseWidget() = default; - -void OTCNegotiationResponseWidget::setOffer(const bs::network::otc::Offer &offer) -{ - receivedOffer_ = offer; - - auto price = bs::network::otc::fromCents(offer.price); - auto amount = bs::network::otc::satToBtc(offer.amount); - const QString offerAndCurrency = QLatin1String("%1 %2"); - const bool isSell = offer.ourSide == bs::network::otc::Side::Sell; - - if (isSell) { - ui_->recieveValue->setText(offerAndCurrency.arg(UiUtils::displayCurrencyAmount(price * amount)).arg(buyProduct_)); - ui_->deliverValue->setText(offerAndCurrency.arg(amount).arg(sellProduct_)); - } - else { - ui_->recieveValue->setText(offerAndCurrency.arg(amount).arg(sellProduct_)); - ui_->deliverValue->setText(offerAndCurrency.arg(UiUtils::displayCurrencyAmount(price * amount)).arg(buyProduct_)); - } - - const QString productToPrice = QLatin1String("%1 %2 / 1 %3"); - ui_->priceValue->setText(productToPrice.arg(price).arg(buyProduct_).arg(sellProduct_)); - - ui_->quantitySpinBox->setValue(amount); - ui_->quantitySpinBox->setDisabled(true); - ui_->bidSpinBox->setValue(price); - ui_->bidSpinBox->setEnabled(!isSell); - ui_->offerSpinBox->setValue(price); - ui_->offerSpinBox->setEnabled(isSell); - ui_->receivingAddressWdgt->setVisible(!isSell); - ui_->labelWallet->setText(isSell ? paymentWallet : receivingWallet); - ui_->toolButtonXBTInputs->setVisible(offer.ourSide == bs::network::otc::Side::Sell); - - onChanged(); -} - -bs::network::otc::Offer OTCNegotiationResponseWidget::offer() const -{ - bs::network::otc::Offer result; - result.ourSide = receivedOffer_.ourSide; - result.amount = bs::network::otc::btcToSat(ui_->quantitySpinBox->value()); - if (receivedOffer_.ourSide == bs::network::otc::Side::Sell) { - result.price = bs::network::otc::toCents(ui_->offerSpinBox->value()); - } - else { - result.price = bs::network::otc::toCents(ui_->bidSpinBox->value()); - } - - result.hdWalletId = ui_->comboBoxXBTWallets->currentData(UiUtils::WalletIdRole).toString().toStdString(); - result.authAddress = ui_->authenticationAddressComboBox->currentText().toStdString(); - - if (ui_->receivingAddressComboBox->currentIndex() != 0) { - result.recvAddress = ui_->receivingAddressComboBox->currentText().toStdString(); - } - - result.inputs = selectedUTXOs(); - - auto walletType = UiUtils::getSelectedWalletType(ui_->comboBoxXBTWallets); - if (walletType & UiUtils::WalletsTypes::HardwareSW) { - auto purpose = UiUtils::getHwWalletPurpose(walletType); - result.walletPurpose.reset(new bs::hd::Purpose(purpose)); - } - - return result; -} - -void OTCNegotiationResponseWidget::setPeer(const bs::network::otc::Peer &peer) -{ - const bool isContact = (peer.type == bs::network::otc::PeerType::Contact); - - if (peer.type == bs::network::otc::PeerType::Request) { - ui_->labelQuantityValue->setText(getXBTRange(peer.response.amount)); - ui_->labelBidValue->setText(getCCRange(peer.response.price)); - - ui_->bidSpinBox->setMinimum(bs::network::otc::fromCents(peer.response.price.lower)); - ui_->bidSpinBox->setMaximum(bs::network::otc::fromCents(peer.response.price.upper)); - ui_->offerSpinBox->setMinimum(bs::network::otc::fromCents(peer.response.price.lower)); - ui_->offerSpinBox->setMaximum(bs::network::otc::fromCents(peer.response.price.upper)); - } - - ui_->rangeQuantity->setVisible(!isContact); - ui_->rangeBid->setVisible(!isContact); - - setupTimer({ peer.stateTimestamp, ui_->progressBarTimeLeft, ui_->labelTimeLeft }); - setSelectedInputs(peer.offer.inputs); -} - -void OTCNegotiationResponseWidget::onParentAboutToHide() -{ - clearSelectedInputs(); -} - -void OTCNegotiationResponseWidget::onSyncInterface() -{ - int index = UiUtils::fillHDWalletsComboBox(ui_->comboBoxXBTWallets, getWalletManager(), UiUtils::WalletsTypes::All); - const auto walletId = getWalletManager()->getDefaultSpendWalletId(); - UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWallets, walletId, UiUtils::WalletsTypes::All); - - onCurrentWalletChanged(); - - UiUtils::fillAuthAddressesComboBoxWithSubmitted(ui_->authenticationAddressComboBox, getAuthManager()); - ui_->widgetButtons->setEnabled(ui_->authenticationAddressComboBox->isEnabled()); -} - -void OTCNegotiationResponseWidget::onUpdateBalances() -{ - double currentBalance = getXBTSpendableBalanceFromCombobox(ui_->comboBoxXBTWallets); - QString totalBalance = tr("%1 %2") - .arg(UiUtils::displayAmount(currentBalance)) - .arg(QString::fromStdString(bs::network::XbtCurrency)); - - ui_->labelBalanceValue->setText(totalBalance); -} - -void OTCNegotiationResponseWidget::onChanged() -{ - bool activateAcceptButton = true; - double price = 0.0; - if (receivedOffer_.ourSide == bs::network::otc::Side::Sell) { - price = ui_->offerSpinBox->value(); - } - else { - price = ui_->bidSpinBox->value(); - } - double quantity = ui_->quantitySpinBox->value(); - - if (receivedOffer_.ourSide == bs::network::otc::Side::Sell - && quantity > getXBTSpendableBalanceFromCombobox(ui_->comboBoxXBTWallets)) { - activateAcceptButton = false; - } - else if (receivedOffer_.ourSide == bs::network::otc::Side::Buy - && price * quantity - > getAssetManager()->getBalance(buyProduct_.toStdString(), bs::UTXOReservationManager::kIncludeZcOtc, nullptr)) { - activateAcceptButton = false; - } - - if (receivedOffer_.ourSide == bs::network::otc::Side::Buy && !selectedUTXOs().empty()) { - uint64_t totalSelected = 0; - for (const auto &utxo : selectedUTXOs()) { - totalSelected += utxo.getValue(); - } - // This does not take into account pay-in fee - if (totalSelected < static_cast(receivedOffer_.amount)) { - activateAcceptButton = false; - } - } - - ui_->pushButtonAccept->setEnabled(activateAcceptButton); - - if (receivedOffer_ == offer()) { - ui_->pushButtonAccept->setText(tr("Accept")); - } - else { - ui_->pushButtonAccept->setText(tr("Update")); - } - - // Review updated price, disable wallet details changing - bool walletDetailsFixed = !receivedOffer_.hdWalletId.empty(); - ui_->comboBoxXBTWallets->setEnabled(!walletDetailsFixed); - ui_->receivingAddressComboBox->setEnabled(!walletDetailsFixed); - ui_->toolButtonXBTInputs->setEnabled(!walletDetailsFixed); - ui_->authenticationAddressComboBox->setEnabled(!walletDetailsFixed); - if (walletDetailsFixed) { - UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWallets, receivedOffer_.hdWalletId); - ui_->receivingAddressComboBox->setCurrentText(QString::fromStdString(receivedOffer_.recvAddress)); - ui_->authenticationAddressComboBox->setCurrentText(QString::fromStdString(receivedOffer_.authAddress)); - } -} - -void OTCNegotiationResponseWidget::onAcceptOrUpdateClicked() -{ - QMetaMethod signal = (receivedOffer_ == offer()) - ? QMetaMethod::fromSignal(&OTCNegotiationResponseWidget::responseAccepted) - : QMetaMethod::fromSignal(&OTCNegotiationResponseWidget::responseUpdated); - - - if (receivedOffer_.ourSide == bs::network::otc::Side::Buy) { - signal.invoke(this); - return; - } - - if (!selectedUTXO_.empty()) { - signal.invoke(this); - return; - } - - submitProposal(ui_->comboBoxXBTWallets, bs::XBTAmount(ui_->quantitySpinBox->value()), - [caller = QPointer(this), signal]() { - if (!caller) { - return; - } - signal.invoke(caller); - }); -} - -void OTCNegotiationResponseWidget::onShowXBTInputsClicked() -{ - ui_->toolButtonXBTInputs->setEnabled(false); - showXBTInputsClicked(ui_->comboBoxXBTWallets); -} - -void OTCNegotiationResponseWidget::onXbtInputsProcessed() -{ - onUpdateBalances(); - ui_->toolButtonXBTInputs->setEnabled(true); - - // Check selected amount and update accept button enabled state - onChanged(); -} - -void OTCNegotiationResponseWidget::onCurrentWalletChanged() -{ - auto recvHdWallet = getCurrentHDWalletFromCombobox(ui_->comboBoxXBTWallets); - if (!recvHdWallet) { - return; - } - if (!recvHdWallet->canMixLeaves()) { - auto xbtGroup = recvHdWallet->getGroup(recvHdWallet->getXBTGroupType()); - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWallets); - UiUtils::fillRecvAddressesComboBox(ui_->receivingAddressComboBox, { xbtGroup->getLeaf(purpose) }); - } - else { - UiUtils::fillRecvAddressesComboBoxHDWallet(ui_->receivingAddressComboBox, recvHdWallet, true); - } - - clearSelectedInputs(); - onUpdateBalances(); -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.h deleted file mode 100644 index 6d619888e..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTC_NEGOTIATION_RESPONSE_WIDGET_H__ -#define __OTC_NEGOTIATION_RESPONSE_WIDGET_H__ - -#include -#include - -#include "OtcTypes.h" -#include "OTCWindowsAdapterBase.h" -#include "CommonTypes.h" - -namespace Ui { - class OTCNegotiationResponseWidget; -}; - -class OTCNegotiationResponseWidget : public OTCWindowsAdapterBase -{ -Q_OBJECT -Q_DISABLE_COPY(OTCNegotiationResponseWidget) - -public: - explicit OTCNegotiationResponseWidget(QWidget* parent = nullptr); - ~OTCNegotiationResponseWidget() override; - - void setOffer(const bs::network::otc::Offer &offer); - - bs::network::otc::Offer offer() const; - void setPeer(const bs::network::otc::Peer &peer) override; - -signals: - void responseAccepted(); - void responseUpdated(); - void responseRejected(); - -public slots: - void onParentAboutToHide() override; - -protected slots: - void onSyncInterface() override; - void onUpdateBalances() override; - -private slots: - void onChanged(); - void onAcceptOrUpdateClicked(); - void onShowXBTInputsClicked(); - void onXbtInputsProcessed(); - - void onCurrentWalletChanged(); - -private: - std::unique_ptr ui_; - bs::network::otc::Offer receivedOffer_; -}; - -#endif // __OTC_NEGOTIATION_RESPONSE_WIDGET_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.ui b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.ui deleted file mode 100644 index 538e49ec1..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCNegotiationResponseWidget.ui +++ /dev/null @@ -1,970 +0,0 @@ - - - - OTCNegotiationResponseWidget - - - - 0 - 0 - 558 - 961 - - - - Form - - - - 5 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - OTC RESPONSE - - - true - - - - - - - - - - OTC DETAILS - - - true - - - - 10 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Receive - - - - - - - xx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Deliver - - - - - - - yy - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Price - - - - - - - zz - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity (XBT) - - - - - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Bid (EUR) - - - - - - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - UPDATE RESPONSE - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 5 - - - - - - 0 - 0 - - - - - 3 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - Quantity - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - 8 - - - 0.000000000000000 - - - 1000000.000000000000000 - - - 5.000000000000000 - - - 10000.000000000000000 - - - - - - - - - - - 5 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Bid - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - 2 - - - 1.000000000000000 - - - 1000000.000000000000000 - - - 5.000000000000000 - - - 10000.000000000000000 - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Offer - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - 2 - - - 1.000000000000000 - - - 1000000.000000000000000 - - - 5.000000000000000 - - - 10000.000000000000000 - - - - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 3 - - - 5 - - - 5 - - - 5 - - - 10 - - - - - Balance - - - - - - - font-weight: bold; - - - xxx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - SETTLEMENT DETAILS - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 0 - - - - - Payment Wallet - - - - - - - 5 - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - 100 - 21 - - - - - 100 - 21 - - - - &Inputs - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Receiving Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - - 0 - - - - - Authentication Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - - - - - 5 - - - 5 - - - - - Qt::Vertical - - - - 20 - 496 - - - - - - - - - 0 - 7 - - - - - 16777215 - 7 - - - - 24 - - - Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing - - - false - - - false - - - - - - - - - - Qt::AlignCenter - - - - - - - - 0 - 1 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 3 - - - - - - 0 - 35 - - - - - - - PushButton - - - - - - - - 0 - 35 - - - - - - - PushButton - - - true - - - true - - - true - - - - - - - - - - - diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCShield.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCShield.cpp deleted file mode 100644 index 9728340d6..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCShield.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCShield.h" -#include "AuthAddressManager.h" -#include "Wallets/SyncWalletsManager.h" - -namespace { - const QString shieldLoginToAccessOTC = QObject::tr("Login to access OTC chat"); - const QString shieldOtcAvailableToTradingParticipants = QObject::tr("OTC available to Trading Participants"); - const QString shieldCounterPartyIsntTradingParticipant = QObject::tr("Counterparty isn't a Trading Participant"); - const QString shieldContactIsOffline = QObject::tr("Contact is offline"); - const QString shieldOtcAvailableOnlyForAcceptedContacts = QObject::tr("OTC available only for Accepted contacts"); - const QString shieldOtcSetupTransactionData = QObject::tr("Setup OTC transaction data"); - const QString shieldOtcshowAvailableOnceAccepted = QObject::tr("OTC settlement available once contact request is accepted"); - - const QString tradingKeyWord = QObject::tr("trading"); - - const QString publicChatHeader = QObject::tr("Public chat"); - const QString privateChatHeader = QObject::tr("Private chat"); - const QString otcSettlementHeader = QObject::tr("OTC settlement"); - - const QString publicChatExplanation = QObject::tr("ChatID is a hash of the users email\n" - "Public rooms are unencrypted\n" - "(Trolls will be banned)\n"); - const QString privateChatExplanation = QObject::tr("Communication is end-to-end encrypted with the users key-pair(s)\n"); - const QString otcSettlementExplanation = QObject::tr("Negotiate and settle XBT/EUR with your chat contacts\n" - "Prices and volumes are not disclosed on exchange\n"); -} - -OTCShield::OTCShield(QWidget* parent) - : WalletShieldBase(parent) -{ - tabType_ = tradingKeyWord; -} - -OTCShield::~OTCShield() noexcept = default; - -void OTCShield::showLoginToAccessOTC() -{ - showShield(shieldLoginToAccessOTC); -} - -void OTCShield::showOtcAvailableToTradingParticipants() -{ - showShield(shieldOtcAvailableToTradingParticipants); -} - -void OTCShield::showCounterPartyIsntTradingParticipant() -{ - showShield(shieldCounterPartyIsntTradingParticipant); -} - -void OTCShield::showContactIsOffline() -{ - showShield(shieldContactIsOffline); -} - -void OTCShield::showOtcAvailableOnlyForAcceptedContacts() -{ - showShield(shieldOtcAvailableOnlyForAcceptedContacts); -} - -void OTCShield::showOtcSetupTransaction() -{ - showShield(shieldOtcSetupTransactionData); -} - -void OTCShield::showChatExplanation() -{ - showThreeBlockShield(publicChatHeader, publicChatExplanation, - privateChatHeader, privateChatExplanation, - otcSettlementHeader, otcSettlementExplanation); -} - -void OTCShield::showShieldOtcAvailableOnceAccepted() -{ - showShield(shieldOtcshowAvailableOnceAccepted); -} - -bool OTCShield::onRequestCheckWalletSettings() -{ - return checkWalletSettings(productType_, product_); -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCShield.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCShield.h deleted file mode 100644 index cd070e898..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCShield.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTC_SHIELD_H__ -#define __OTC_SHIELD_H__ - -#include "WalletShieldBase.h" - -class OTCShield : public WalletShieldBase -{ -Q_OBJECT - -public: - explicit OTCShield(QWidget* parent = nullptr ); - ~OTCShield() noexcept override; - - void showLoginToAccessOTC(); - void showOtcAvailableToTradingParticipants(); - void showCounterPartyIsntTradingParticipant(); - void showContactIsOffline(); - void showOtcAvailableOnlyForAcceptedContacts(); - void showOtcSetupTransaction(); - void showChatExplanation(); - void showShieldOtcAvailableOnceAccepted(); - -public slots: - bool onRequestCheckWalletSettings(); - -private: - - const ProductType productType_ = ProductType::SpotXBT; - const QString product_ = QLatin1String("XBT/EUR"); -}; - -#endif // __OTC_SHIELD_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.cpp deleted file mode 100644 index 5a6a7f76f..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.cpp +++ /dev/null @@ -1,320 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCWindowsAdapterBase.h" -#include "OTCWindowsManager.h" - -#include "UiUtils.h" -#include "Wallets/SyncWalletsManager.h" -#include "Wallets/SyncHDWallet.h" -#include "AuthAddressManager.h" -#include "AssetManager.h" -#include "CoinControlDialog.h" -#include "SelectedTransactionInputs.h" -#include "TradesUtils.h" -#include "UtxoReservationManager.h" -#include "XBTAmount.h" -#include "BSMessageBox.h" - -#include -#include -#include - -namespace { - const std::chrono::milliseconds kTimerRepeatTimeMSec{ 500 }; - const QString secondsRemaining = QObject::tr("second(s) remaining"); -} - -OTCWindowsAdapterBase::OTCWindowsAdapterBase(QWidget* parent /*= nullptr*/) - : QWidget(parent) -{ - connect(&timeoutTimer_, &QTimer::timeout, this, &OTCWindowsAdapterBase::onUpdateTimerData); - timeoutTimer_.setInterval(kTimerRepeatTimeMSec); -} - -void OTCWindowsAdapterBase::setChatOTCManager(const std::shared_ptr& otcManager) -{ - otcManager_ = otcManager; - connect(otcManager_.get(), &OTCWindowsManager::syncInterfaceRequired, this, [this]() { - onSyncInterface(); - }); - - connect(otcManager_.get(), &OTCWindowsManager::updateMDDataRequired, this, - [this](bs::network::Asset::Type type, const QString& security, const bs::network::MDFields& fields) - { - onUpdateMD(type, security, fields); - }); - - connect(otcManager_.get(), &OTCWindowsManager::updateBalances, this, [this]() { - onUpdateBalances(); - }); -} - -std::shared_ptr OTCWindowsAdapterBase::getWalletManager() const -{ - return otcManager_->getWalletManager(); -} - -std::shared_ptr OTCWindowsAdapterBase::getAuthManager() const -{ - return otcManager_->getAuthManager(); -} - -std::shared_ptr OTCWindowsAdapterBase::getAssetManager() const -{ - return otcManager_->getAssetManager(); -} - -std::shared_ptr OTCWindowsAdapterBase::getUtxoManager() const -{ - return otcManager_->getUtxoManager(); -} - -void OTCWindowsAdapterBase::setPeer(const bs::network::otc::Peer &) -{ -} - -bs::UtxoReservationToken OTCWindowsAdapterBase::releaseReservation() -{ - return std::move(reservation_); -} - -void OTCWindowsAdapterBase::setReservation(bs::UtxoReservationToken&& reservation) -{ - reservation_ = std::move(reservation); -} - -void OTCWindowsAdapterBase::onSyncInterface() -{ -} - -void OTCWindowsAdapterBase::onUpdateMD(bs::network::Asset::Type type, const QString& security, const bs::network::MDFields& fields) -{ - if (productGroup_ != type || security_ != security) { - return; - } - - updateIndicativePrices(type, security, fields); - - // overloaded in direved class - onMDUpdated(); -} - -void OTCWindowsAdapterBase::onMDUpdated() -{ -} - -void OTCWindowsAdapterBase::onUpdateBalances() -{ -} - -void OTCWindowsAdapterBase::showXBTInputsClicked(QComboBox *walletsCombobox) -{ - reservation_.release(); - showXBTInputs(walletsCombobox); -} - -void OTCWindowsAdapterBase::showXBTInputs(QComboBox *walletsCombobox) -{ - const bool useAutoSel = selectedUTXO_.empty(); - - - const auto &hdWallet = getCurrentHDWalletFromCombobox(walletsCombobox); - - std::vector allUTXOs; - if (!hdWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(walletsCombobox); - allUTXOs = getUtxoManager()->getAvailableXbtUTXOs(hdWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcOtc); - } - else { - allUTXOs = getUtxoManager()->getAvailableXbtUTXOs(hdWallet->walletId(), bs::UTXOReservationManager::kIncludeZcOtc); - } - - auto inputs = std::make_shared(allUTXOs); - - // Set this to false is needed otherwise current selection would be cleared - inputs->SetUseAutoSel(useAutoSel); - - if (!useAutoSel) { - for (const auto &utxo : selectedUTXO_) { - inputs->SetUTXOSelection(utxo.getTxHash(), utxo.getTxOutIndex()); - } - } - - CoinControlDialog dialog(inputs, true, this); - int rc = dialog.exec(); - if (rc != QDialog::Accepted) { - emit xbtInputsProcessed(); - return; - } - - auto selectedInputs = dialog.selectedInputs(); - if (bs::UtxoReservation::instance()->containsReservedUTXO(selectedInputs)) { - BSMessageBox(BSMessageBox::critical, tr("UTXO reservation failed"), - tr("Some of selected UTXOs has been already reserved"), this).exec(); - showXBTInputs(walletsCombobox); - return; - } - selectedUTXO_ = std::move(selectedInputs); - if (!selectedUTXO_.empty()) { - reservation_ = getUtxoManager()->makeNewReservation(selectedUTXO_); - } - - emit xbtInputsProcessed(); -} - -void OTCWindowsAdapterBase::onUpdateTimerData() -{ - if (!currentTimeoutData_.progressBarTimeLeft_ || !currentTimeoutData_.labelTimeLeft_) { - timeoutTimer_.stop(); - return; - } - - const auto currentOfferEndTimestamp = currentTimeoutData_.offerTimestamp_ + std::chrono::seconds(timeoutSec_); - const auto diff = currentOfferEndTimestamp - std::chrono::steady_clock::now(); - const auto diffSeconds = std::chrono::duration_cast(diff); - - currentTimeoutData_.labelTimeLeft_->setText(QString(QLatin1String("%1 %2")).arg(diffSeconds.count()).arg(secondsRemaining)); - currentTimeoutData_.progressBarTimeLeft_->setMaximum(timeoutSec_.count()); - currentTimeoutData_.progressBarTimeLeft_->setValue(diffSeconds.count()); - - if (diffSeconds.count() < 0) { - timeoutTimer_.stop(); - } -} - -void OTCWindowsAdapterBase::updateIndicativePrices(bs::network::Asset::Type type, const QString& security - , const bs::network::MDFields& fields) -{ - for (const auto &field : fields) { - switch (field.type) { - case bs::network::MDField::PriceBid: - sellIndicativePrice_ = field.value; - break; - case bs::network::MDField::PriceOffer: - buyIndicativePrice_ = field.value; - break; - default: break; - } - } -} - -BTCNumericTypes::balance_type OTCWindowsAdapterBase::getXBTSpendableBalanceFromCombobox(QComboBox *walletsCombobox) const -{ - const auto hdWallet = getCurrentHDWalletFromCombobox(walletsCombobox); - if (!hdWallet) { - return .0; - } - - BTCNumericTypes::balance_type totalBalance{}; - if (selectedUTXO_.empty()) { - BTCNumericTypes::satoshi_type sum = 0; - if (!hdWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(walletsCombobox); - sum = getUtxoManager()->getAvailableXbtUtxoSum(hdWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcOtc); - } - else { - sum = getUtxoManager()->getAvailableXbtUtxoSum(hdWallet->walletId(), bs::UTXOReservationManager::kIncludeZcOtc); - } - - return bs::XBTAmount(sum).GetValueBitcoin(); - } - else { - for (const auto &utxo : selectedUTXO_) { - totalBalance += bs::XBTAmount(utxo.getValue()).GetValueBitcoin(); - } - } - - return totalBalance; -} - -std::shared_ptr OTCWindowsAdapterBase::getCurrentHDWalletFromCombobox(QComboBox *walletsCombobox) const -{ - const auto walletId = walletsCombobox->currentData(UiUtils::WalletIdRole).toString().toStdString(); - return getWalletManager()->getHDWalletById(walletId); -} - -void OTCWindowsAdapterBase::submitProposal(QComboBox *walletsCombobox, bs::XBTAmount amount, CbSuccess onSuccess) -{ - const auto hdWallet = getCurrentHDWalletFromCombobox(walletsCombobox); - if (!hdWallet) { - return; - } - - auto cbUtxoSet = [caller = QPointer(this), cbSuccess = std::move(onSuccess)](std::vector&& utxos) { - if (!caller) { - return; - } - - caller->setSelectedInputs(utxos); - caller->setReservation(caller->getUtxoManager()->makeNewReservation(utxos)); - - cbSuccess(); - }; - - auto checkAmount = bs::UTXOReservationManager::CheckAmount::Enabled; - - if (!hdWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(walletsCombobox); - getUtxoManager()->getBestXbtUtxoSet(hdWallet->walletId(), purpose, amount.GetValue() - , std::move(cbUtxoSet), true, checkAmount); - } - else { - getUtxoManager()->getBestXbtUtxoSet(hdWallet->walletId(), amount.GetValue() - , std::move(cbUtxoSet), true, checkAmount, bs::UTXOReservationManager::kIncludeZcOtc); - } -} - -QString OTCWindowsAdapterBase::getXBTRange(bs::network::otc::Range xbtRange) -{ - return QStringLiteral("%1 - %2") - .arg(UiUtils::displayCurrencyAmount(xbtRange.lower)) - .arg(UiUtils::displayCurrencyAmount(xbtRange.upper)); -} - -QString OTCWindowsAdapterBase::getCCRange(bs::network::otc::Range ccRange) -{ - return QStringLiteral("%1 - %2") - .arg(UiUtils::displayCurrencyAmount(bs::network::otc::fromCents(ccRange.lower))) - .arg(UiUtils::displayCurrencyAmount(bs::network::otc::fromCents(ccRange.upper))); -} - -QString OTCWindowsAdapterBase::getSide(bs::network::otc::Side requestSide, bool isOwnRequest) -{ - if (!isOwnRequest) { - requestSide = bs::network::otc::switchSide(requestSide); - } - - return QString::fromStdString(bs::network::otc::toString(requestSide)); -} - -void OTCWindowsAdapterBase::clearSelectedInputs() -{ - selectedUTXO_.clear(); - reservation_ = {}; -} - -void OTCWindowsAdapterBase::setupTimer(TimeoutData&& timeoutData) -{ - currentTimeoutData_ = std::move(timeoutData); - onUpdateTimerData(); - timeoutTimer_.start(); -} - -std::chrono::seconds OTCWindowsAdapterBase::getSeconds(std::chrono::milliseconds durationInMillisecs) -{ - return std::chrono::duration_cast(durationInMillisecs); -} - -void OTCWindowsAdapterBase::setSelectedInputs(const std::vector& selectedUTXO) -{ - selectedUTXO_ = selectedUTXO; - -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.h deleted file mode 100644 index 49393ac13..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsAdapterBase.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTCWINDOWSMANAGER_H__ -#define __OTCWINDOWSMANAGER_H__ - -#include -#include "CommonTypes.h" -#include "OtcTypes.h" -#include "ValidityFlag.h" -#include "UtxoReservationToken.h" - -#include -#include -#include - -class QComboBox; -class OTCWindowsManager; -class AuthAddressManager; -class AssetManager; -class QLabel; -class QProgressBar; - -namespace bs { - namespace sync { - class WalletsManager; - - namespace hd { - class Wallet; - } - } - - namespace network { - namespace otc { - struct Peer; - } - } - class UTXOReservationManager; -} - -struct TimeoutData -{ - std::chrono::steady_clock::time_point offerTimestamp_{}; - QPointer progressBarTimeLeft_{}; - QPointer labelTimeLeft_{}; -}; - -using CbSuccess = std::function; -class OTCWindowsAdapterBase : public QWidget { - Q_OBJECT -public: - OTCWindowsAdapterBase(QWidget* parent = nullptr); - ~OTCWindowsAdapterBase() override = default; - - void setChatOTCManager(const std::shared_ptr& otcManager); - std::shared_ptr getWalletManager() const; - std::shared_ptr getAuthManager() const; - std::shared_ptr getAssetManager() const; - std::shared_ptr getUtxoManager() const; - - virtual void setPeer(const bs::network::otc::Peer &); - - bs::UtxoReservationToken releaseReservation(); - void setReservation(bs::UtxoReservationToken&& reservation); - -signals: - - void xbtInputsProcessed(); - -public slots: - virtual void onAboutToApply() {} - virtual void onChatRoomChanged() {} - virtual void onParentAboutToHide() {} - -protected slots: - virtual void onSyncInterface(); - void onUpdateMD(bs::network::Asset::Type, const QString&, const bs::network::MDFields&); - virtual void onMDUpdated(); - virtual void onUpdateBalances(); - void onUpdateTimerData(); - -protected: - // Shared function between children - void showXBTInputsClicked(QComboBox *walletsCombobox); - - void updateIndicativePrices( - bs::network::Asset::Type type - , const QString& security - , const bs::network::MDFields& fields); - - BTCNumericTypes::balance_type getXBTSpendableBalanceFromCombobox(QComboBox *walletsCombobox) const; - std::shared_ptr getCurrentHDWalletFromCombobox(QComboBox *walletsCombobox) const; - - void submitProposal(QComboBox *walletsCombobox, bs::XBTAmount amount, CbSuccess onSuccess); - - QString getXBTRange(bs::network::otc::Range xbtRange); - QString getCCRange(bs::network::otc::Range ccRange); - - QString getSide(bs::network::otc::Side requestSide, bool isOwnRequest); - - const std::vector &selectedUTXOs() const { return selectedUTXO_; } - void clearSelectedInputs(); - void setSelectedInputs(const std::vector& selectedUTXO); - - void setupTimer(TimeoutData&& timeoutData); - std::chrono::seconds getSeconds(std::chrono::milliseconds durationInMillisecs); - -protected: - std::shared_ptr otcManager_{}; - - bs::network::Asset::Type productGroup_ = bs::network::Asset::SpotXBT; - // #new_logic : fix security & product checking - QString security_{ QLatin1String("XBT/EUR") }; - QString sellProduct_{ QLatin1String("XBT") }; - QString buyProduct_{ QLatin1String("EUR") }; - double sellIndicativePrice_{}; - double buyIndicativePrice_{}; - - ValidityFlag validityFlag_; - std::chrono::seconds timeoutSec_{}; - - std::vector selectedUTXO_; - bs::UtxoReservationToken reservation_; - -private: - void showXBTInputs(QComboBox *walletsCombobox); - - QTimer timeoutTimer_; - TimeoutData currentTimeoutData_{}; -}; - -#endif // __OTCWINDOWSMANAGER_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsManager.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsManager.cpp deleted file mode 100644 index 69fa6cc55..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsManager.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OTCWindowsManager.h" -#include "Wallets/SyncWalletsManager.h" -#include "AuthAddressManager.h" -#include "MDCallbacksQt.h" -#include "AssetManager.h" -#include "UtxoReservationManager.h" - -OTCWindowsManager::OTCWindowsManager(QObject* parent /*= nullptr*/) -{ -} - -void OTCWindowsManager::init(const std::shared_ptr& walletsMgr - , const std::shared_ptr &authManager - , const std::shared_ptr &mdCallbacks - , const std::shared_ptr& assetManager - , const std::shared_ptr &armory - , const std::shared_ptr &utxoReservationManager) -{ - // #new_logic : we shouldn't send aggregated signal for all events - - walletsMgr_ = walletsMgr; - - // Do not listen for walletChanged (too verbose and resets UI too often) and walletsReady (to late and resets UI after startup unexpectedly) - connect(walletsMgr_.get(), &bs::sync::WalletsManager::AuthLeafCreated, this, &OTCWindowsManager::syncInterfaceRequired); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletPromotedToPrimary, this, &OTCWindowsManager::syncInterfaceRequired); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletDeleted, this, &OTCWindowsManager::syncInterfaceRequired); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletAdded, this, &OTCWindowsManager::syncInterfaceRequired); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletsSynchronized, this, &OTCWindowsManager::syncInterfaceRequired); - connect(walletsMgr_.get(), &bs::sync::WalletsManager::authWalletChanged, this, &OTCWindowsManager::syncInterfaceRequired); - - connect(walletsMgr_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, &OTCWindowsManager::updateBalances); - - authManager_ = authManager; - connect(authManager_.get(), &AuthAddressManager::AddressListUpdated, this, &OTCWindowsManager::syncInterfaceRequired); - - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, this, &OTCWindowsManager::updateMDDataRequired); - - assetManager_ = assetManager; - connect(assetManager_.get(), &AssetManager::totalChanged, this, &OTCWindowsManager::updateBalances); - connect(assetManager_.get(), &AssetManager::securitiesChanged, this, &OTCWindowsManager::updateBalances); - - armory_ = armory; - utxoReservationManager_ = utxoReservationManager; - connect(utxoReservationManager_.get(), &bs::UTXOReservationManager::availableUtxoChanged, this, &OTCWindowsManager::updateBalances); - - emit syncInterfaceRequired(); -} - -std::shared_ptr OTCWindowsManager::getWalletManager() const -{ - return walletsMgr_; -} - -std::shared_ptr OTCWindowsManager::getAuthManager() const -{ - return authManager_; -} - -std::shared_ptr OTCWindowsManager::getAssetManager() const -{ - return assetManager_; -} - -std::shared_ptr OTCWindowsManager::getArmory() const -{ - return armory_; -} - -std::shared_ptr OTCWindowsManager::getUtxoManager() const -{ - return utxoReservationManager_; -} - diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsManager.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsManager.h deleted file mode 100644 index 2f7cc3abc..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/OTCWindowsManager.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __OTCWINDOWSADAPTERBASE_H__ -#define __OTCWINDOWSADAPTERBASE_H__ - -#include -#include "QObject" -#include "CommonTypes.h" - -namespace bs { - namespace sync { - class WalletsManager; - } -} -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class MDCallbacksQt; - -namespace bs { - class UTXOReservationManager; -} - -class OTCWindowsManager : public QObject { - Q_OBJECT -public: - OTCWindowsManager(QObject* parent = nullptr); - ~OTCWindowsManager() override = default; - - void init(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); - std::shared_ptr getWalletManager() const; - std::shared_ptr getAuthManager() const; - std::shared_ptr getAssetManager() const; - std::shared_ptr getArmory() const; - std::shared_ptr getUtxoManager() const; - -signals: - void syncInterfaceRequired(); - void updateMDDataRequired(bs::network::Asset::Type, const QString &, const bs::network::MDFields&); - void updateBalances(); - -protected: - std::shared_ptr walletsMgr_; - std::shared_ptr authManager_; - std::shared_ptr assetManager_; - std::shared_ptr armory_; - std::shared_ptr utxoReservationManager_; -}; - -#endif // __OTCWINDOWSADAPTERBASE_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.cpp b/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.cpp deleted file mode 100644 index 46902e4c8..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "PullOwnOTCRequestWidget.h" - -#include "OtcTypes.h" -#include "UiUtils.h" -#include "ui_PullOwnOTCRequestWidget.h" - -using namespace bs::network; - -namespace { - const QString headerTextOTCRequest = QObject::tr("OTC REQUEST"); - const QString headerTextOTCResponse = QObject::tr("OTC RESPONSE"); - const QString headerTextOTCPendingBuyerSign = QObject::tr("OTC PENDING SETTLEMENT PAY-IN"); - const QString headerTextOTCPendingSellerSign = QObject::tr("OTC PENDING SETTLEMENT PAY-OUT"); - - const QString buttonTextPull = QObject::tr("PULL"); - const QString buttonTextCancel = QObject::tr("CANCEL"); -} - -PullOwnOTCRequestWidget::PullOwnOTCRequestWidget(QWidget* parent) - : OTCWindowsAdapterBase(parent) - , ui_{ new Ui::PullOwnOTCRequestWidget() } -{ - ui_->setupUi(this); - connect(ui_->pullPushButton, &QPushButton::clicked, this, &PullOwnOTCRequestWidget::currentRequestPulled); -} - -PullOwnOTCRequestWidget::~PullOwnOTCRequestWidget() = default; - -void PullOwnOTCRequestWidget::setOffer(const bs::network::otc::Offer &offer) -{ - setupNegotiationInterface(headerTextOTCRequest); - setupOfferInfo(offer, true); - timeoutSec_ = getSeconds(bs::network::otc::negotiationTimeout()); -} - -void PullOwnOTCRequestWidget::setRequest(const bs::network::otc::QuoteRequest &request) -{ - setupNegotiationInterface(headerTextOTCRequest); - - ourSide_ = request.ourSide; - ui_->sideValue->setText(QString::fromStdString(otc::toString(request.ourSide))); - ui_->quantityValue->setText(QString::fromStdString(otc::toString(request.rangeType))); - ui_->priceValue->clear(); - ui_->priceWidget->hide(); - ui_->totalWidget->hide(); - - timeoutSec_ = getSeconds(bs::network::otc::publicRequestTimeout()); -} - -void PullOwnOTCRequestWidget::setResponse(const otc::QuoteResponse &response) -{ - setupNegotiationInterface(headerTextOTCResponse, true /* isResponse */); - - ourSide_ = response.ourSide; - ui_->sideValue->setText(QString::fromStdString(otc::toString(response.ourSide))); - ui_->quantityValue->setText(getXBTRange(response.amount)); - ui_->priceValue->setText(getCCRange(response.price)); - ui_->priceWidget->show(); - ui_->totalWidget->hide(); - - timeoutSec_ = std::chrono::seconds(0); -} - -void PullOwnOTCRequestWidget::setPendingBuyerSign(const bs::network::otc::Offer &offer) -{ - setupSignAwaitingInterface(headerTextOTCPendingSellerSign); - setupOfferInfo(offer, true); - timeoutSec_ = getSeconds(bs::network::otc::payoutTimeout()); -} - -void PullOwnOTCRequestWidget::setPendingSellerSign(const bs::network::otc::Offer &offer) -{ - setupSignAwaitingInterface(headerTextOTCPendingBuyerSign); - setupOfferInfo(offer, false); - timeoutSec_ = getSeconds(bs::network::otc::payinTimeout()); -} - -void PullOwnOTCRequestWidget::setPeer(const bs::network::otc::Peer &peer) -{ - using namespace bs::network::otc; - if ((peer.state == State::WaitBuyerSign && ourSide_ == otc::Side::Buy) || - (peer.state == State::WaitSellerSeal && ourSide_ == otc::Side::Sell)) { - timeoutSec_ = std::chrono::seconds(0); - ui_->progressBarTimeLeft->hide(); - ui_->labelTimeLeft->hide(); - ui_->horizontalWidgetSubmit->hide(); - } - - if (peer.state != State::Idle) { - ui_->sideValue->setText(getSide(ourSide_, peer.isOurSideSentOffer)); - } - - if (timeoutSec_.count()) { - setupTimer({ peer.stateTimestamp, ui_->progressBarTimeLeft, ui_->labelTimeLeft }); - } -} - -void PullOwnOTCRequestWidget::setupNegotiationInterface(const QString& headerText, bool isResponse /* = false */) -{ - ui_->progressBarTimeLeft->setVisible(!isResponse); - ui_->labelTimeLeft->setVisible(!isResponse); - ui_->horizontalWidgetSubmit->show(); - ui_->pullPushButton->setText(buttonTextPull); - ui_->headerLabel->setText(headerText); -} - -void PullOwnOTCRequestWidget::setupSignAwaitingInterface(const QString& headerText) -{ - ui_->progressBarTimeLeft->show(); - ui_->labelTimeLeft->show(); - ui_->horizontalWidgetSubmit->show(); - ui_->pullPushButton->setText(buttonTextCancel); - ui_->headerLabel->setText(headerText); -} - -void PullOwnOTCRequestWidget::setupOfferInfo(const bs::network::otc::Offer &offer, bool allowCancel) -{ - ourSide_ = offer.ourSide; - ui_->sideValue->setText(QString::fromStdString(otc::toString(offer.ourSide))); - - auto price = bs::network::otc::fromCents(offer.price); - auto amount = bs::network::otc::satToBtc(offer.amount); - ui_->quantityValue->setText(UiUtils::displayAmount(amount)); - ui_->priceValue->setText(UiUtils::displayCurrencyAmount(price)); - ui_->totalWidget->show(); - ui_->totalValue->setText(UiUtils::displayCurrencyAmount(price * amount)); - - ui_->priceWidget->show(); - ui_->pullPushButton->setVisible(allowCancel); -} diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.h b/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.h deleted file mode 100644 index 7d1d7c902..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __PULL_OWN_OTC_REQUEST_WIDGET_H__ -#define __PULL_OWN_OTC_REQUEST_WIDGET_H__ - -#include -#include -#include -#include - -#include "OtcTypes.h" -#include "OTCWindowsAdapterBase.h" - -namespace Ui { - class PullOwnOTCRequestWidget; -}; - -class PullOwnOTCRequestWidget : public OTCWindowsAdapterBase -{ -Q_OBJECT - -public: - explicit PullOwnOTCRequestWidget(QWidget* parent = nullptr); - ~PullOwnOTCRequestWidget() override; - - void setOffer(const bs::network::otc::Offer &offer); - void setRequest(const bs::network::otc::QuoteRequest &request); - void setResponse(const bs::network::otc::QuoteResponse &response); - void setPendingBuyerSign(const bs::network::otc::Offer &offer); - void setPendingSellerSign(const bs::network::otc::Offer &offer); - - void setPeer(const bs::network::otc::Peer &peer) override; - -signals: - void currentRequestPulled(); - - void saveOfflineClicked(); - void loadOfflineClicked(); - void broadcastOfflineClicked(); - -protected: - void setupNegotiationInterface(const QString& headerText, bool isResponse = false); - void setupSignAwaitingInterface(const QString& headerText); - void setupOfferInfo(const bs::network::otc::Offer &offer, bool allowCancel); - -private: - std::unique_ptr ui_; - bs::network::otc::Side ourSide_ = bs::network::otc::Side::Unknown; -}; - -#endif // __PULL_OWN_OTC_REQUEST_WIDGET_H__ diff --git a/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.ui b/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.ui deleted file mode 100644 index 3c6bb51db..000000000 --- a/BlockSettleUILib/ChatUI/OTCShieldWidgets/PullOwnOTCRequestWidget.ui +++ /dev/null @@ -1,508 +0,0 @@ - - - - PullOwnOTCRequestWidget - - - - 0 - 0 - 460 - 724 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - OTC REQUEST - - - true - - - - - - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - OTC DETAILS - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Product Group - - - - - - - XBT - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Security ID - - - - - - - XBT/EUR - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Side - - - - - - - --- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity (XBT) - - - - - - - --- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Price (EUR) - - - - - - - --- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Total Value (EUR) - - - - - - - --- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - - - - - 5 - - - 5 - - - - - Qt::Vertical - - - - 20 - 496 - - - - - - - - - 0 - 7 - - - - - 16777215 - 7 - - - - 24 - - - Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing - - - false - - - false - - - - - - - - - - Qt::AlignCenter - - - - - - - - 0 - 1 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 35 - - - - - - - PULL - - - - - - - - - - - diff --git a/BlockSettleUILib/ChatUI/PartyTreeItem.cpp b/BlockSettleUILib/ChatUI/PartyTreeItem.cpp deleted file mode 100644 index fa06f7ac5..000000000 --- a/BlockSettleUILib/ChatUI/PartyTreeItem.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "PartyTreeItem.h" - -using namespace bs; - -PartyTreeItem::PartyTreeItem(const QVariant& data, UI::ElementType modelType, PartyTreeItem* parent /*= nullptr*/) - : itemData_(data) - , modelType_(modelType) - , parentItem_(parent) -{ -} - -PartyTreeItem::~PartyTreeItem() -{ -} - -PartyTreeItem* PartyTreeItem::child(int number) -{ - Q_ASSERT(number >= 0 && number < childItems_.size()); - return childItems_[number].get(); -} - -int PartyTreeItem::childCount() const -{ - return childItems_.size(); -} - -int PartyTreeItem::columnCount() const -{ - return 1; -} - -QVariant PartyTreeItem::data() const -{ - return itemData_; -} - -bool PartyTreeItem::insertChildren(std::unique_ptr&& item) -{ - childItems_.push_back(std::move(item)); - return true; -} - -PartyTreeItem* PartyTreeItem::parent() -{ - return parentItem_; -} - -void PartyTreeItem::removeAll() -{ - childItems_.clear(); -} - -int PartyTreeItem::childNumber() const -{ - if (parentItem_) { - for (int iChild = 0; iChild < parentItem_->childCount(); ++iChild) { - if (parentItem_->childItems_[iChild].get() != this) { - continue; - } - - return iChild; - } - } - - Q_ASSERT(false); - return 0; -} - -bool PartyTreeItem::setData(const QVariant& value) -{ - itemData_ = value; - return true; -} - -UI::ElementType PartyTreeItem::modelType() const -{ - return modelType_; -} - -void PartyTreeItem::increaseUnseenCounter(int newMessageCount) -{ - Q_ASSERT(newMessageCount > 0); - unseenCounter_ += newMessageCount; -} - -void PartyTreeItem::decreaseUnseenCounter(int seenMessageCount) -{ - unseenCounter_ -= seenMessageCount; - unseenCounter_ = std::max(unseenCounter_, 0); -} - -bool PartyTreeItem::hasNewMessages() const -{ - return unseenCounter_ > 0; -} - -int PartyTreeItem::unseenCount() const -{ - return unseenCounter_; -} - -void PartyTreeItem::enableOTCToggling(bool otcToggling) -{ - otcTogglingMode_ = otcToggling; -} - -bool PartyTreeItem::isOTCTogglingMode() const -{ - // #flickeringOTC Flickering otc awaiting messages - disabled for now - return false; // otcTogglingMode_; -} - -void PartyTreeItem::changeOTCToggleState() -{ - currentOTCToggleState_ = !currentOTCToggleState_; -} - -bool PartyTreeItem::activeOTCToggleState() const -{ - return currentOTCToggleState_; -} - -void PartyTreeItem::applyReusableData(const ReusableItemData& data) -{ - unseenCounter_ = data.unseenCount_; - otcTogglingMode_ = data.otcTogglingMode_; -} - -ReusableItemData PartyTreeItem::generateReusableData() const -{ - return { unseenCounter_ , otcTogglingMode_ }; -} diff --git a/BlockSettleUILib/ChatUI/PartyTreeItem.h b/BlockSettleUILib/ChatUI/PartyTreeItem.h deleted file mode 100644 index 23cb326bf..000000000 --- a/BlockSettleUILib/ChatUI/PartyTreeItem.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef PARTYTREEITEM_H -#define PARTYTREEITEM_H - -#include -#include "QList" - -#include "../BlocksettleNetworkingLib/ChatProtocol/Party.h" -#include "chat.pb.h" -// Internal enum - -namespace bs { - namespace network { - namespace otc { - enum class PeerType : int; - } - } -} - -namespace bs { - namespace UI { - enum class ElementType - { - Root = 0, - Container, - Party, - }; - } -} - -struct ReusableItemData -{ - int unseenCount_{}; - bool otcTogglingMode_{}; -}; - -class PartyTreeItem -{ -public: - PartyTreeItem(const QVariant& data, bs::UI::ElementType modelType, PartyTreeItem* parent = nullptr); - ~PartyTreeItem(); - - PartyTreeItem* child(int number); - - int childCount() const; - int columnCount() const; - - QVariant data() const; - - bool insertChildren(std::unique_ptr&& item); - PartyTreeItem* parent(); - void removeAll(); - int childNumber() const; - bool setData(const QVariant& value); - - bs::UI::ElementType modelType() const; - - void increaseUnseenCounter(int newMessageCount); - void decreaseUnseenCounter(int seenMessageCount); - bool hasNewMessages() const; - int unseenCount() const; - - void enableOTCToggling(bool otcToggling); - bool isOTCTogglingMode() const; - - void changeOTCToggleState(); - bool activeOTCToggleState() const; - - void applyReusableData(const ReusableItemData& data); - ReusableItemData generateReusableData() const; - - bs::network::otc::PeerType peerType{}; - -private: - std::vector> childItems_; - QVariant itemData_; - PartyTreeItem* parentItem_; - bs::UI::ElementType modelType_; - int unseenCounter_{}; - - // OTC toggling - bool otcTogglingMode_{}; - bool currentOTCToggleState_{}; -}; -#endif // PARTYTREEITEM_H diff --git a/BlockSettleUILib/ChatUI/RequestPartyBox.cpp b/BlockSettleUILib/ChatUI/RequestPartyBox.cpp deleted file mode 100644 index 96ba50b24..000000000 --- a/BlockSettleUILib/ChatUI/RequestPartyBox.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RequestPartyBox.h" - -RequestPartyBox::RequestPartyBox(const QString& title, const QString& note, QWidget* parent) - : QDialog(parent), ui_(new Ui::RequestPartyBox) -{ - ui_->setupUi(this); - ui_->labelTitle->setText(title); - ui_->labelNote->setText(note); - resize(width(), 0); - - connect(ui_->pushButtonOk, &QPushButton::clicked, this, &RequestPartyBox::accept); - connect(ui_->plainTextEditMessage, &BSChatInput::sendMessage, this, &RequestPartyBox::accept); - connect(ui_->pushButtonCancel, &QPushButton::clicked, this, &RequestPartyBox::reject); - - setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint); -} - -QString RequestPartyBox::getCustomMessage() const -{ - return ui_->plainTextEditMessage->toPlainText(); -} \ No newline at end of file diff --git a/BlockSettleUILib/ChatUI/RequestPartyBox.h b/BlockSettleUILib/ChatUI/RequestPartyBox.h deleted file mode 100644 index 143e99c52..000000000 --- a/BlockSettleUILib/ChatUI/RequestPartyBox.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef REQUESTPARTY_H -#define REQUESTPARTY_H - -#include -#include -#include "ui_RequestPartyBox.h" - -class RequestPartyBox : public QDialog -{ - Q_OBJECT - -public: - RequestPartyBox(const QString& title, const QString& note, QWidget* parent = nullptr); - QString getCustomMessage() const; - -private: - QString title_; - QString note_; - std::unique_ptr ui_; -}; - -#endif // REQUESTPARTY_H diff --git a/BlockSettleUILib/ChatUI/RequestPartyBox.ui b/BlockSettleUILib/ChatUI/RequestPartyBox.ui deleted file mode 100644 index 803241e79..000000000 --- a/BlockSettleUILib/ChatUI/RequestPartyBox.ui +++ /dev/null @@ -1,361 +0,0 @@ - - - - RequestPartyBox - - - - 0 - 0 - 400 - 251 - - - - - 0 - 0 - - - - - 400 - 0 - - - - - 400 - 16777215 - - - - Friend Request - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - 12 - - - 9 - - - 9 - - - 9 - - - 9 - - - - - - 0 - 0 - - - - - 43 - 43 - - - - - 43 - 43 - - - - - - - :/resources/notification_question.png - - - - - - - title - - - true - - - true - - - true - - - - - - - - - 12 - - - 9 - - - 9 - - - 9 - - - 9 - - - - - - 0 - 0 - - - - - 43 - 0 - - - - - 43 - 16777215 - - - - - - - - Note - - - true - - - - - - - - - 0 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Qt::Vertical - - - - 0 - 40 - - - - - - - - - 0 - 0 - - - - - 0 - 70 - - - - - 16777215 - 16777215 - - - - true - - - false - - - Friend request message... - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 10 - - - 20 - - - 5 - - - 20 - - - 5 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - Cancel - - - - - - - - 120 - 0 - - - - - 120 - 16777215 - - - - OK - - - - - - - - - - - - - - - BSChatInput - QTextBrowser -
ChatUI/BSChatInput.h
-
-
- - - - - -
diff --git a/BlockSettleUILib/ChatUI/SearchWidget.cpp b/BlockSettleUILib/ChatUI/SearchWidget.cpp deleted file mode 100644 index 543d4092e..000000000 --- a/BlockSettleUILib/ChatUI/SearchWidget.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include -#include -#include -#include - -#include "SearchWidget.h" -#include "ui_SearchWidget.h" -#include "UserSearchModel.h" -#include "ChatUI/ChatSearchListViewItemStyle.h" - -#include "chat.pb.h" - -namespace { - constexpr int kShowEmptyFoundUserListTimeoutMs = 3000; - constexpr int kRowHeigth = 20; - constexpr int kUserListPaddings = 6; - constexpr int kMaxVisibleRows = 3; - constexpr int kBottomSpace = 25; - constexpr int kFullHeightMargins = 10; - - const QRegularExpression kRxEmail(QStringLiteral(R"(^[a-z0-9._-]+@([a-z0-9-]+\.)+[a-z]+$)"), - QRegularExpression::CaseInsensitiveOption); -} - -SearchWidget::SearchWidget(QWidget *parent) - : QWidget(parent) - , ui_(new Ui::SearchWidget) - , listVisibleTimer_(new QTimer) - , userSearchModel_(new UserSearchModel) - , emailRegex_(kRxEmail) -{ - ui_->setupUi(this); - - connect(ui_->chatSearchLineEdit, &ChatSearchLineEdit::textEdited, - this, &SearchWidget::onSearchUserTextEdited); - connect(ui_->chatSearchLineEdit, &ChatSearchLineEdit::textChanged, - this, &SearchWidget::searchTextChanged); - connect(ui_->chatSearchLineEdit, &ChatSearchLineEdit::textChanged, - this, &SearchWidget::onInputTextChanged); - connect(ui_->chatSearchLineEdit, &ChatSearchLineEdit::keyDownPressed, - this, &SearchWidget::onFocusResults); - connect(ui_->chatSearchLineEdit, &ChatSearchLineEdit::keyEnterPressed, - this, &SearchWidget::onFocusResults); - connect(ui_->chatSearchLineEdit, &ChatSearchLineEdit::keyEscapePressed, - this, &SearchWidget::onCloseResult); - - connect(ui_->searchResultTreeView, &ChatSearchListVew::customContextMenuRequested, - this, &SearchWidget::onShowContextMenu); - connect(ui_->searchResultTreeView, &ChatSearchListVew::activated, - this, &SearchWidget::onItemClicked); - connect(ui_->searchResultTreeView, &ChatSearchListVew::clicked, - this, &SearchWidget::onItemClicked); - connect(ui_->searchResultTreeView, &ChatSearchListVew::leaveRequired, - this, &SearchWidget::onLeaveSearchResults); - connect(ui_->searchResultTreeView, &ChatSearchListVew::leaveWithCloseRequired, - this, &SearchWidget::onLeaveAndCloseSearchResults); - - onSetListVisible(false); - assert(emailRegex_.isValid()); -} - -SearchWidget::~SearchWidget() = default; - -bool SearchWidget::eventFilter(QObject *watched, QEvent *event) -{ - if (ui_->searchResultTreeView->isVisible() && event->type() == QEvent::MouseButtonRelease) { - QPoint pos = ui_->searchResultTreeView->mapFromGlobal(QCursor::pos()); - - if (!ui_->searchResultTreeView->rect().contains(pos)) { - onSetListVisible(false); - } - } - - return QWidget::eventFilter(watched, event); -} - -void SearchWidget::init(const Chat::ChatClientServicePtr& chatClientServicePtr) -{ - chatClientServicePtr_ = chatClientServicePtr; - connect(chatClientServicePtr_.get(), &Chat::ChatClientService::searchUserReply, this, &SearchWidget::onSearchUserReply); - - //chatClient_ = chatClient; - //ui_->chatSearchLineEdit->setActionsHandler(chatClient); - userSearchModel_->setItemStyle(std::make_shared()); - ui_->searchResultTreeView->setModel(userSearchModel_.get()); - - //connect(chatClient_.get(), &ChatClient::SearchUserListReceived, this, &SearchWidget::onSearchUserListReceived); - - connect(userSearchModel_.get(), &QAbstractItemModel::rowsInserted, - this, &SearchWidget::onResetTreeView); - connect(userSearchModel_.get(), &QAbstractItemModel::rowsRemoved, - this, &SearchWidget::onResetTreeView); - connect(userSearchModel_.get(), &QAbstractItemModel::modelReset, - this, &SearchWidget::onResetTreeView); - - setMaximumHeight(kBottomSpace + kRowHeigth * kMaxVisibleRows + - ui_->chatSearchLineEdit->height() + kUserListPaddings + kFullHeightMargins); - setMinimumHeight(kBottomSpace + ui_->chatSearchLineEdit->height()); - - ui_->searchResultTreeView->setVisible(false); - ui_->notFoundLabel->setVisible(false); - - qApp->installEventFilter(this); - - listVisibleTimer_->setSingleShot(true); - connect(listVisibleTimer_.get(), &QTimer::timeout, [this] { - onSetListVisible(false); - }); -} - -bool SearchWidget::isLineEditEnabled() const -{ - return ui_->chatSearchLineEdit->isEnabled(); -} - -bool SearchWidget::isListVisible() const -{ - return ui_->searchResultTreeView->isVisible(); -} - -QString SearchWidget::searchText() const -{ - return ui_->chatSearchLineEdit->text(); -} - -void SearchWidget::onClearLineEdit() -{ - ui_->chatSearchLineEdit->clear(); -} - -void SearchWidget::onStartListAutoHide() -{ - listVisibleTimer_->start(kShowEmptyFoundUserListTimeoutMs); -} - -void SearchWidget::onSetLineEditEnabled(bool value) -{ - ui_->chatSearchLineEdit->setEnabled(value); -} - -void SearchWidget::onSetListVisible(bool value) -{ - auto *result = ui_->searchResultTreeView; - bool hasUsers = result->model() && result->model()->rowCount() > 0; - - result->setVisible(value && hasUsers); - result->scrollToTop(); - result->setCurrentIndex(QModelIndex()); - ui_->notFoundLabel->setVisible(value && !hasUsers); - layout()->update(); - listVisibleTimer_->stop(); - - // hide popup after a few sec - if (value && !hasUsers) { - onStartListAutoHide(); - } -} - -void SearchWidget::onSetSearchText(QString value) -{ - ui_->chatSearchLineEdit->setText(value); -} - -void SearchWidget::onResetTreeView() -{ - int rowCount = ui_->searchResultTreeView->model()->rowCount(); - int visibleRows = rowCount >= kMaxVisibleRows ? kMaxVisibleRows : rowCount; - ui_->searchResultTreeView->setFixedHeight(kRowHeigth * visibleRows + kUserListPaddings); -} - -void SearchWidget::onShowContextMenu(const QPoint &pos) -{ - QScopedPointer menu(new QMenu()); - auto index = ui_->searchResultTreeView->indexAt(pos); - if (!index.isValid()) { - return; - } - - onItemClicked(index); -} - -void SearchWidget::onFocusResults() -{ - if (ui_->searchResultTreeView->isVisible()) { - ui_->searchResultTreeView->setFocus(); - auto index = ui_->searchResultTreeView->model()->index(0, 0); - ui_->searchResultTreeView->setCurrentIndex(index); - return; - } - - onSetListVisible(true); -} - -void SearchWidget::onCloseResult() -{ - onSetListVisible(false); -} - -void SearchWidget::onItemClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - - const QString id = index.data(Qt::DisplayRole).toString(); - const auto status = index.data(UserSearchModel::UserStatusRole).value(); - switch (status) - { - case UserSearchModel::UserStatus::ContactUnknown: - case UserSearchModel::UserStatus::ContactRejected: - { - emit contactFriendRequest(id); - } - break; - case UserSearchModel::UserStatus::ContactAccepted: - case UserSearchModel::UserStatus::ContactPendingIncoming: - case UserSearchModel::UserStatus::ContactPendingOutgoing: - { - emit showUserRoom(id); - } - break; - default: - return; - } - - onSetListVisible(false); - onSetSearchText({}); -} - -void SearchWidget::onLeaveSearchResults() -{ - ui_->chatSearchLineEdit->setFocus(); - ui_->searchResultTreeView->clearSelection(); - auto currentIndex = ui_->searchResultTreeView->currentIndex(); - ui_->searchResultTreeView->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::Deselect); -} - -void SearchWidget::onLeaveAndCloseSearchResults() -{ - ui_->chatSearchLineEdit->setFocus(); - onSetListVisible(false); -} - -void SearchWidget::onInputTextChanged(const QString &text) -{ - if (text.isEmpty()) { - onSetListVisible(false); - } -} - -void SearchWidget::onSearchUserTextEdited() -{ - onSetListVisible(false); - std::string userToAdd = searchText().toStdString(); - - if (userToAdd.empty() || userToAdd.length() < 3) { - onSetListVisible(false); - userSearchModel_->setUsers({}); - return; - } - - QRegularExpressionMatch match = emailRegex_.match(QString::fromStdString(userToAdd)); - if (match.hasMatch()) { - emit emailHashRequested(userToAdd); - return; - } - - // ! Feature: Think how to prevent spamming server - - sendSearchRequest(userToAdd); -} - -void SearchWidget::onSearchUserReply(const Chat::SearchUserReplyList& userHashList, const std::string& searchId) -{ - if (searchId != lastSearchId_) { - return; - } - - Chat::ClientPartyModelPtr clientPartyModelPtr = chatClientServicePtr_->getClientPartyModelPtr(); - std::vector userInfoList; - - for (const auto& userHash : userHashList) { - Chat::PrivatePartyState privatePartyState = clientPartyModelPtr->deducePrivatePartyStateForUser(userHash); - - auto status = UserSearchModel::UserStatus::ContactUnknown; - switch (privatePartyState) - { - case Chat::PrivatePartyState::RequestedOutgoing: - status = UserSearchModel::UserStatus::ContactPendingOutgoing; - break; - case Chat::PrivatePartyState::RequestedIncoming: - status = UserSearchModel::UserStatus::ContactPendingIncoming; - break; - case Chat::PrivatePartyState::Rejected: - status = UserSearchModel::UserStatus::ContactRejected; - break; - case Chat::PrivatePartyState::Initialized: - status = UserSearchModel::UserStatus::ContactAccepted; - break; - default: - break; - } - - userInfoList.emplace_back(QString::fromStdString(userHash), status); - } - - userSearchModel_->setUsers(userInfoList); - - bool visible = !userInfoList.empty(); - onSetListVisible(visible); - - // hide popup after a few sec - if (visible && userInfoList.empty()) { - onStartListAutoHide(); - } -} - -void SearchWidget::onEmailHashReceived(const std::string &email, const std::string &hash) -{ - if (searchText().toStdString() != email) { - return; - } - - sendSearchRequest(hash); -} - -void SearchWidget::sendSearchRequest(const std::string &text) -{ - QUuid uid = QUuid::createUuid(); - lastSearchId_ = uid.toString(QUuid::WithoutBraces).toStdString(); - chatClientServicePtr_->SearchUser(text, lastSearchId_); -} diff --git a/BlockSettleUILib/ChatUI/SearchWidget.h b/BlockSettleUILib/ChatUI/SearchWidget.h deleted file mode 100644 index 6afcf8dfe..000000000 --- a/BlockSettleUILib/ChatUI/SearchWidget.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef SEARCHWIDGET_H -#define SEARCHWIDGET_H - -#include - -#include -#include - -#include "ChatProtocol/ChatClientService.h" - -class QAbstractItemModel; -class ChatClient; -class ChatSearchActionsHandler; -class UserSearchModel; - -namespace Ui { - class SearchWidget; -} - -namespace Chat { - class UserData; - class Data; -} - -class SearchWidget : public QWidget -{ - Q_OBJECT - -/* Properties Section Begin */ - Q_PROPERTY(bool lineEditEnabled - READ isLineEditEnabled - WRITE onSetLineEditEnabled - STORED false) - Q_PROPERTY(bool listVisible - READ isListVisible - WRITE onSetListVisible - STORED false) - Q_PROPERTY(QString searchText - READ searchText - WRITE onSetSearchText - NOTIFY searchTextChanged - USER true - STORED false) - -public: - bool isLineEditEnabled() const; - bool isListVisible() const; - QString searchText() const; - -public slots: - void onSetLineEditEnabled(bool value); - void onSetListVisible(bool value); - void onSetSearchText(QString value); - -signals: - void searchTextChanged(QString searchText); - void emailHashRequested(const std::string &email); -/* Properties Section End */ - -public: - explicit SearchWidget(QWidget *parent = nullptr); - ~SearchWidget() override; - - bool eventFilter(QObject *watched, QEvent *event) override; - - void init(const Chat::ChatClientServicePtr& chatClientServicePtr); - -public slots: - void onClearLineEdit(); - void onStartListAutoHide(); - void onSearchUserReply(const Chat::SearchUserReplyList& userHashList, const std::string& searchId); - void onEmailHashReceived(const std::string &email, const std::string &hash); - -private slots: - void onResetTreeView(); - void onShowContextMenu(const QPoint &pos); - void onFocusResults(); - void onCloseResult(); - void onItemClicked(const QModelIndex &index); - void onLeaveSearchResults(); - void onLeaveAndCloseSearchResults(); - void onInputTextChanged(const QString &text); - void onSearchUserTextEdited(); - -signals: - void contactFriendRequest(const QString &userID); - void showUserRoom(const QString &userID); - -private: - void sendSearchRequest(const std::string &text); - - QScopedPointer ui_; - QScopedPointer listVisibleTimer_; - QScopedPointer userSearchModel_; - Chat::ChatClientServicePtr chatClientServicePtr_; - std::string lastSearchId_; - - QRegularExpression emailRegex_; - -}; - -#endif // SEARCHWIDGET_H diff --git a/BlockSettleUILib/ChatUI/SearchWidget.ui b/BlockSettleUILib/ChatUI/SearchWidget.ui deleted file mode 100644 index d75c2d152..000000000 --- a/BlockSettleUILib/ChatUI/SearchWidget.ui +++ /dev/null @@ -1,160 +0,0 @@ - - - - SearchWidget - - - - 0 - 0 - 226 - 159 - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 2 - - - 0 - - - 5 - - - 0 - - - - - - - - Enter email or email hash - - - - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - User not found - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 10 - - - - - - - - - - - ChatSearchLineEdit - QLineEdit -
ChatUI/ChatSearchLineEdit.h
-
- - ChatSearchListVew - QTreeView -
ChatUI/ChatSearchListVew.h
-
-
- - -
diff --git a/BlockSettleUILib/CoinControlDialog.cpp b/BlockSettleUILib/CoinControlDialog.cpp index 3b2acb570..43ba44a4d 100644 --- a/BlockSettleUILib/CoinControlDialog.cpp +++ b/BlockSettleUILib/CoinControlDialog.cpp @@ -11,7 +11,7 @@ #include "ui_CoinControlDialog.h" #include "CoinControlDialog.h" #include -#include "SelectedTransactionInputs.h" +#include "Wallets/SelectedTransactionInputs.h" CoinControlDialog::CoinControlDialog(const std::shared_ptr &inputs, bool allowAutoSel, QWidget* parent) diff --git a/BlockSettleUILib/CoinControlModel.cpp b/BlockSettleUILib/CoinControlModel.cpp index f6e30cd19..efa8e374f 100644 --- a/BlockSettleUILib/CoinControlModel.cpp +++ b/BlockSettleUILib/CoinControlModel.cpp @@ -15,7 +15,7 @@ #include #include "BTCNumericTypes.h" #include "BtcUtils.h" -#include "SelectedTransactionInputs.h" +#include "Wallets/SelectedTransactionInputs.h" #include "TxClasses.h" #include "UiUtils.h" #include "Wallets/SyncWallet.h" @@ -599,8 +599,10 @@ void CoinControlModel::loadInputs(const std::shared_ptrGetWallet(); addressNode = new AddressNode(CoinControlNode::Type::DoesNotMatter, QString::fromStdString(address.display()) - , QString::fromStdString(selectedInputs->GetWallet()->getAddressComment(bs::Address::fromHash(input.getRecipientScrAddr()))), row, cpfp_.get()); + , QString::fromStdString(wallet ? wallet->getAddressComment(bs::Address::fromHash(input.getRecipientScrAddr())) : "") + , row, cpfp_.get()); cpfp_->appendChildNode(addressNode); cpfpNodes_[addrStr] = addressNode; } diff --git a/BlockSettleUILib/CoinControlWidget.cpp b/BlockSettleUILib/CoinControlWidget.cpp index 720d1452c..b643d0aea 100644 --- a/BlockSettleUILib/CoinControlWidget.cpp +++ b/BlockSettleUILib/CoinControlWidget.cpp @@ -13,7 +13,7 @@ #include "UiUtils.h" #include "CoinControlModel.h" -#include "SelectedTransactionInputs.h" +#include "Wallets/SelectedTransactionInputs.h" #include #include diff --git a/BlockSettleUILib/CommonMessageBoxDialog.h b/BlockSettleUILib/CommonMessageBoxDialog.h index c086f8240..e255cdf8e 100644 --- a/BlockSettleUILib/CommonMessageBoxDialog.h +++ b/BlockSettleUILib/CommonMessageBoxDialog.h @@ -25,4 +25,4 @@ Q_OBJECT void UpdateSize(); }; -#endif // __COMMON_MESSAGE_BOX_DIALOG_H__ \ No newline at end of file +#endif // __COMMON_MESSAGE_BOX_DIALOG_H__ diff --git a/BlockSettleUILib/CreatePrimaryWalletPrompt.cpp b/BlockSettleUILib/CreatePrimaryWalletPrompt.cpp index 99bac58a8..537e78bca 100644 --- a/BlockSettleUILib/CreatePrimaryWalletPrompt.cpp +++ b/BlockSettleUILib/CreatePrimaryWalletPrompt.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/CreatePrimaryWalletPrompt.h b/BlockSettleUILib/CreatePrimaryWalletPrompt.h index ded9e1979..7f926a9fd 100644 --- a/BlockSettleUILib/CreatePrimaryWalletPrompt.h +++ b/BlockSettleUILib/CreatePrimaryWalletPrompt.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/CreatePrimaryWalletPrompt.ui b/BlockSettleUILib/CreatePrimaryWalletPrompt.ui index 970923658..a6f1a35b5 100644 --- a/BlockSettleUILib/CreatePrimaryWalletPrompt.ui +++ b/BlockSettleUILib/CreatePrimaryWalletPrompt.ui @@ -2,7 +2,7 @@ CreateTransactionDialogSimple @@ -16,8 +6,8 @@ 0 0 - 715 - 381 + 824 + 452 @@ -798,6 +788,78 @@ + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + 75 + true + + + + Wallet unlock + + + + + + + + 0 + 0 + + + + Passphrase: + + + + + + + + 0 + 0 + + + + QLineEdit::Password + + + + + + + false + + + + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + diff --git a/BlockSettleUILib/DialogManager.cpp b/BlockSettleUILib/DialogManager.cpp index 99a0ae06a..b7931ccf1 100644 --- a/BlockSettleUILib/DialogManager.cpp +++ b/BlockSettleUILib/DialogManager.cpp @@ -23,8 +23,7 @@ namespace { DialogManager::DialogManager(const QWidget *mainWindow) : mainWindow_(mainWindow) -{ -} +{} void DialogManager::adjustDialogPosition(QDialog *dlg) { diff --git a/BlockSettleUILib/EditContactDialog.cpp b/BlockSettleUILib/EditContactDialog.cpp deleted file mode 100644 index 29829900e..000000000 --- a/BlockSettleUILib/EditContactDialog.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "EditContactDialog.h" -#include "ui_EditContactDialog.h" - -EditContactDialog::EditContactDialog(const QString &contactId - , const QString &displayName - , const QDateTime ×tamp - , const QString &idKey - , QWidget *parent) : - QDialog(parent) - , ui_(new Ui::EditContactDialog()) - , contactId_(contactId) - , displayName_(displayName) - , timestamp_(timestamp) - , idKey_(idKey) -{ - ui_->setupUi(this); - - refillFields(); - connect(ui_->buttonBox, &QDialogButtonBox::accepted, this, &EditContactDialog::accept); - - ui_->nameOptionalLineEdit->setFocus(); - ui_->nameOptionalLineEdit->selectAll(); -} - -EditContactDialog::~EditContactDialog() noexcept = default; - -QString EditContactDialog::contactId() const -{ - return contactId_; -} - -QString EditContactDialog::displayName() const -{ - return displayName_; -} - -QDateTime EditContactDialog::timestamp() const -{ - return timestamp_; -} - -QString EditContactDialog::idKey() const -{ - return idKey_; -} - -void EditContactDialog::accept() -{ - displayName_ = ui_->nameOptionalLineEdit->text(); - QDialog::accept(); -} - -void EditContactDialog::reject() -{ - refillFields(); - QDialog::reject(); -} - -void EditContactDialog::showEvent(QShowEvent *event) -{ - Q_UNUSED(event) - auto dialogCenter = window()->mapToGlobal(window()->rect().center()); - auto parentWindow = parentWidget()->window(); - auto parentWindowCenter = parentWindow->mapToGlobal(parentWindow->rect().center()); - if (parentWindowCenter == dialogCenter) { - move(parentWindowCenter - window()->rect().center()); - } else { - move(parentWindowCenter - dialogCenter); - } -} - -void EditContactDialog::refillFields() -{ - ui_->nameOptionalLineEdit->setText(displayName_); - ui_->userIDLineEdit->setText(contactId_); - if (timestamp_.isValid()) { - ui_->contactDateLineEdit->setText(timestamp_.toString(Qt::SystemLocaleShortDate)); - } - ui_->iDKeyLineEdit->setText(idKey_); -} diff --git a/BlockSettleUILib/EditContactDialog.h b/BlockSettleUILib/EditContactDialog.h deleted file mode 100644 index 168ef17c8..000000000 --- a/BlockSettleUILib/EditContactDialog.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef EDITCONTACTDIALOG_H -#define EDITCONTACTDIALOG_H - -#include -#include - -#include - -namespace Ui { -class EditContactDialog; -} - -class EditContactDialog : public QDialog -{ - Q_OBJECT - -public: - explicit EditContactDialog( - const QString &contactId - , const QString &displayName = QString() - , const QDateTime ×tamp = QDateTime() - , const QString &idKey = QString() - , QWidget *parent = nullptr); - ~EditContactDialog() noexcept override; - - QString contactId() const; - QString displayName() const; - QDateTime timestamp() const; - QString idKey() const; - -public slots: - void accept() override; - void reject() override; - -protected: - void showEvent(QShowEvent *event) override; - -private: - void refillFields(); - -private: - std::unique_ptr ui_; - QString contactId_; - QString displayName_; - QDateTime timestamp_; - QString idKey_; -}; - -#endif // EDITCONTACTDIALOG_H diff --git a/BlockSettleUILib/EditContactDialog.ui b/BlockSettleUILib/EditContactDialog.ui deleted file mode 100644 index 1c332ab25..000000000 --- a/BlockSettleUILib/EditContactDialog.ui +++ /dev/null @@ -1,346 +0,0 @@ - - - - EditContactDialog - - - - 0 - 0 - 595 - 182 - - - - - 225 - 0 - - - - Edit contact - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 15 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - - 15 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Contact Details - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 115 - 0 - - - - - 115 - 16777215 - - - - Name (optional) - - - - - - - Enter contact name... - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 115 - 0 - - - - - 115 - 16777215 - - - - User ID - - - - - - - -- - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 115 - 0 - - - - - 115 - 16777215 - - - - ID Key - - - - - - - -- - - - true - - - - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 115 - 0 - - - - - 115 - 16777215 - - - - Time Stamp - - - - - - - -- - - - true - - - - - - - - - - - - - - - - - - - true - - - - 5 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - QDialogButtonBox::Ok - - - - - - - - - - - diff --git a/BlockSettleUILib/ExplorerWidget.cpp b/BlockSettleUILib/ExplorerWidget.cpp index 655218d08..730cbcd5c 100644 --- a/BlockSettleUILib/ExplorerWidget.cpp +++ b/BlockSettleUILib/ExplorerWidget.cpp @@ -10,7 +10,6 @@ */ #include "ExplorerWidget.h" #include "ui_ExplorerWidget.h" -#include "AuthAddressManager.h" #include "BSMessageBox.h" #include "TransactionDetailsWidget.h" #include "UiUtils.h" @@ -65,28 +64,18 @@ ExplorerWidget::ExplorerWidget(QWidget *parent) : ExplorerWidget::~ExplorerWidget() = default; -// Initialize the widget and related widgets (block, address, Tx). Blocks won't -// be set up for now. -void ExplorerWidget::init(const std::shared_ptr &armory - , const std::shared_ptr &inLogger - , const std::shared_ptr &walletsMgr - , const std::shared_ptr &ccFileMgr - , const std::shared_ptr &authMgr) +void ExplorerWidget::init(const std::shared_ptr &logger) { - logger_ = inLogger; - authMgr_ = authMgr; - ui_->Transaction->init(armory, inLogger, walletsMgr, ccFileMgr->getResolver()); - ui_->Address->init(armory, inLogger, ccFileMgr->getResolver(), walletsMgr); -// ui_->Block->init(armory, inLogger); + logger_ = logger; + ui_->Transaction->init(logger); + ui_->Address->init(logger); - connect(authMgr_.get(), &AuthAddressManager::gotBsAddressList, [this] { - ui_->Address->setBSAuthAddrs(authMgr_->GetBSAddresses()); - }); - - // With Armory and the logger set, we can start accepting text input. ui_->searchBox->setReadOnly(false); - ui_->searchBox->setPlaceholderText(QString::fromStdString( - "Search for a transaction or address.")); + ui_->searchBox->setPlaceholderText(tr("Search for a transaction or address")); + + connect(ui_->Address, &AddressDetailsWidget::needAddressHistory, this, &ExplorerWidget::needAddressHistory); + connect(ui_->Address, &AddressDetailsWidget::needTXDetails, this, &ExplorerWidget::needTXDetails); + connect(ui_->Transaction, &TransactionDetailsWidget::needTXDetails, this, &ExplorerWidget::needTXDetails); } void ExplorerWidget::shortcutActivated(ShortcutType) @@ -101,6 +90,27 @@ void ExplorerWidget::mousePressEvent(QMouseEvent *event) } } +void ExplorerWidget::onNewBlock(unsigned int blockNum) +{ + ui_->Address->onNewBlock(blockNum); + ui_->Transaction->onNewBlock(blockNum); +} + +void ExplorerWidget::onAddressHistory(const bs::Address& addr, uint32_t curBlock, const std::vector& entries) +{ + ui_->Address->onAddressHistory(addr, curBlock, entries); +} + +void ExplorerWidget::onTXDetails(const std::vector& txDet) +{ + if (ui_->stackedWidget->currentIndex() == AddressPage) { + ui_->Address->onTXDetails(txDet); + } + else if (ui_->stackedWidget->currentIndex() == TxPage) { + ui_->Transaction->onTXDetails(txDet); + } +} + // The function called when the user uses the search bar (Tx or address). void ExplorerWidget::onSearchStarted(bool saveToHistory) { diff --git a/BlockSettleUILib/ExplorerWidget.h b/BlockSettleUILib/ExplorerWidget.h index 5500fae76..8175e8296 100644 --- a/BlockSettleUILib/ExplorerWidget.h +++ b/BlockSettleUILib/ExplorerWidget.h @@ -13,6 +13,7 @@ #include "TabWithShortcut.h" #include "ArmoryConnection.h" +#include "Wallets/SignerDefs.h" #include #include @@ -36,11 +37,7 @@ Q_OBJECT ExplorerWidget(QWidget *parent = nullptr); ~ExplorerWidget() override; - void init(const std::shared_ptr &armory - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); + void init(const std::shared_ptr&); void shortcutActivated(ShortcutType s) override; enum Page { @@ -51,6 +48,15 @@ Q_OBJECT void mousePressEvent(QMouseEvent *event) override; + void onNewBlock(unsigned int blockNum); + void onAddressHistory(const bs::Address&, uint32_t curBlock + , const std::vector&); + void onTXDetails(const std::vector&); + +signals: + void needAddressHistory(const bs::Address&); + void needTXDetails(const std::vector&, bool useCache, const bs::Address&); + protected slots: void onSearchStarted(bool saveToHistory); void onExpTimeout(); diff --git a/BlockSettleUILib/ImportKeyBox.cpp b/BlockSettleUILib/ImportKeyBox.cpp index 4bd4c6c71..6c03cc647 100644 --- a/BlockSettleUILib/ImportKeyBox.cpp +++ b/BlockSettleUILib/ImportKeyBox.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/ImportKeyBox.h b/BlockSettleUILib/ImportKeyBox.h index 01ef4bd78..30abf6d5e 100644 --- a/BlockSettleUILib/ImportKeyBox.h +++ b/BlockSettleUILib/ImportKeyBox.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/ImportKeyBox.ui b/BlockSettleUILib/ImportKeyBox.ui index 796c98eb3..de5221145 100644 --- a/BlockSettleUILib/ImportKeyBox.ui +++ b/BlockSettleUILib/ImportKeyBox.ui @@ -2,7 +2,7 @@ APISettingsPage diff --git a/BlockSettleUILib/Settings/ArmoryServersViewModel.cpp b/BlockSettleUILib/Settings/ArmoryServersViewModel.cpp index 4b1ed2f0d..dc106ddaf 100644 --- a/BlockSettleUILib/Settings/ArmoryServersViewModel.cpp +++ b/BlockSettleUILib/Settings/ArmoryServersViewModel.cpp @@ -23,6 +23,10 @@ ArmoryServersViewModel::ArmoryServersViewModel(const std::shared_ptr(ArmoryServersViewModel::ColumnsCount); @@ -37,7 +41,7 @@ QVariant ArmoryServersViewModel::data(const QModelIndex &index, int role) const { if (index.row() >= servers_.size()) return QVariant(); ArmoryServer server = servers_.at(index.row()); - int currentServerIndex = serversProvider_->indexOfCurrent(); + const int currentServerIndex = serversProvider_ ? serversProvider_->indexOfCurrent() : currentServerIndex_; QString serverNetType = (server.netType == NetworkType::MainNet ? tr("MainNet") : tr("TestNet")); if (role == Qt::FontRole && index.row() == currentServerIndex) { @@ -110,9 +114,16 @@ void ArmoryServersViewModel::setSingleColumnMode(bool singleColumnMode) singleColumnMode_ = singleColumnMode; } +void ArmoryServersViewModel::onArmoryServers(const QList& servers + , int idxCur, int idxConn) +{ + currentServerIndex_ = idxCur; + beginResetModel(); + servers_ = servers; + endResetModel(); +} + void ArmoryServersViewModel::setHighLightSelectedServer(bool highLightSelectedServer) { highLightSelectedServer_ = highLightSelectedServer; } - - diff --git a/BlockSettleUILib/Settings/ArmoryServersViewModel.h b/BlockSettleUILib/Settings/ArmoryServersViewModel.h index aadca5f3b..04da7fe70 100644 --- a/BlockSettleUILib/Settings/ArmoryServersViewModel.h +++ b/BlockSettleUILib/Settings/ArmoryServersViewModel.h @@ -15,7 +15,6 @@ #include #include "AuthAddress.h" -#include "AuthAddressManager.h" #include "BinaryData.h" #include "ApplicationSettings.h" #include "ArmoryServersProvider.h" @@ -24,8 +23,9 @@ class ArmoryServersViewModel : public QAbstractTableModel { public: - ArmoryServersViewModel(const std::shared_ptr& serversProvider + [[deprecated]] ArmoryServersViewModel(const std::shared_ptr& serversProvider , QObject *parent = nullptr); + ArmoryServersViewModel(QObject* parent = nullptr); ~ArmoryServersViewModel() noexcept = default; ArmoryServersViewModel(const ArmoryServersViewModel&) = delete; @@ -43,6 +43,8 @@ class ArmoryServersViewModel : public QAbstractTableModel void setHighLightSelectedServer(bool highLightSelectedServer); void setSingleColumnMode(bool singleColumnMode); + void onArmoryServers(const QList&, int idxCur, int idxConn); + public slots: void update(); @@ -51,6 +53,7 @@ public slots: QList servers_; bool highLightSelectedServer_ = true; bool singleColumnMode_ = false; + int currentServerIndex_{ 0 }; enum ArmoryServersViewViewColumns : int { diff --git a/BlockSettleUILib/Settings/ArmoryServersWidget.cpp b/BlockSettleUILib/Settings/ArmoryServersWidget.cpp index fd31fddbe..a79c284f7 100644 --- a/BlockSettleUILib/Settings/ArmoryServersWidget.cpp +++ b/BlockSettleUILib/Settings/ArmoryServersWidget.cpp @@ -56,39 +56,10 @@ ArmoryServersWidget::ArmoryServersWidget(const std::shared_ptrtableViewArmory->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this](const QItemSelection &selected, const QItemSelection &deselected){ - // this check will prevent loop selectionChanged -> setupServerFromSelected -> select -> selectionChanged - if (deselected.isEmpty()) { - return; - } - - bool isEmpty = ui_->tableViewArmory->selectionModel()->selectedIndexes().isEmpty(); - ui_->pushButtonDeleteServer->setDisabled(isEmpty); - ui_->pushButtonEditServer->setDisabled(isEmpty); - ui_->pushButtonConnect->setDisabled(isEmpty); - ui_->pushButtonSelectServer->setDisabled(isEmpty); - - if (!isEmpty && selected.indexes().first().row() < ArmoryServersProvider::kDefaultServersCount) { - ui_->pushButtonDeleteServer->setDisabled(true); - ui_->pushButtonEditServer->setDisabled(true); - } - - resetForm(); - - // save to settings right after row highlight - setupServerFromSelected(true); - }); - - connect(ui_->comboBoxNetworkType, QOverload::of(&QComboBox::currentIndexChanged), - [this](int index){ - if (index == 1) { - ui_->spinBoxPort->setValue(appSettings_->GetDefaultArmoryRemotePort(NetworkType::MainNet)); - } - else if (index == 2){ - ui_->spinBoxPort->setValue(appSettings_->GetDefaultArmoryRemotePort(NetworkType::TestNet)); - } - }); + connect(ui_->tableViewArmory->selectionModel(), &QItemSelectionModel::selectionChanged + , this, &ArmoryServersWidget::onSelectionChanged); + connect(ui_->comboBoxNetworkType, QOverload::of(&QComboBox::currentIndexChanged) + , this, &ArmoryServersWidget::onCurIndexChanged); resetForm(); @@ -114,16 +85,103 @@ ArmoryServersWidget::ArmoryServersWidget(const std::shared_ptrpushButtonKeyImport->hide(); } +ArmoryServersWidget::ArmoryServersWidget(QWidget* parent) + : QWidget(parent) + , ui_(new Ui::ArmoryServersWidget) +{ + armoryServersModel_ = new ArmoryServersViewModel(this); + ui_->setupUi(this); + + ui_->pushButtonConnect->setVisible(false); + ui_->spinBoxPort->setValue(kArmoryDefaultMainNetPort); + + ui_->tableViewArmory->setModel(armoryServersModel_); + ui_->tableViewArmory->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + // int defaultSectionSize = ui_->tableViewArmory->horizontalHeader()->defaultSectionSize(); + // ui_->tableViewArmory->horizontalHeader()->resizeSection(0, defaultSectionSize * 2); + // ui_->tableViewArmory->horizontalHeader()->resizeSection(1, defaultSectionSize); + // ui_->tableViewArmory->horizontalHeader()->resizeSection(2, defaultSectionSize); + // ui_->tableViewArmory->horizontalHeader()->resizeSection(3, defaultSectionSize); + ui_->tableViewArmory->horizontalHeader()->setStretchLastSection(true); + + isStartupDialog_ = false; + + connect(ui_->pushButtonAddServer, &QPushButton::clicked, this, &ArmoryServersWidget::onAddServer); + connect(ui_->pushButtonDeleteServer, &QPushButton::clicked, this, &ArmoryServersWidget::onDeleteServer); + connect(ui_->pushButtonEditServer, &QPushButton::clicked, this, &ArmoryServersWidget::onEdit); + connect(ui_->pushButtonSelectServer, &QPushButton::clicked, this, &ArmoryServersWidget::onSelect); + connect(ui_->pushButtonConnect, &QPushButton::clicked, this, &ArmoryServersWidget::onConnect); + connect(ui_->pushButtonCancelSaveServer, &QPushButton::clicked, this, &ArmoryServersWidget::resetForm); + connect(ui_->pushButtonSaveServer, &QPushButton::clicked, this, &ArmoryServersWidget::onSave); + connect(ui_->lineEditAddress, &QLineEdit::textChanged, this, &ArmoryServersWidget::onFormChanged); + + QRegExp rx(kRxAddress); + ui_->lineEditAddress->setValidator(new QRegExpValidator(rx, this)); + onFormChanged(); + + connect(ui_->pushButtonClose, &QPushButton::clicked, this, [this]() { + emit needClose(); + }); + + connect(ui_->tableViewArmory->selectionModel(), &QItemSelectionModel::selectionChanged + , this, &ArmoryServersWidget::onSelectionChanged); + connect(ui_->comboBoxNetworkType, QOverload::of(&QComboBox::currentIndexChanged) + , this, &ArmoryServersWidget::onCurIndexChanged); + + resetForm(); + + connect(ui_->lineEditName, &QLineEdit::textEdited, this, &ArmoryServersWidget::updateSaveButton); + connect(ui_->lineEditAddress, &QLineEdit::textEdited, this, &ArmoryServersWidget::updateSaveButton); + connect(ui_->comboBoxNetworkType, &QComboBox::currentTextChanged, this, &ArmoryServersWidget::updateSaveButton); + connect(ui_->spinBoxPort, QOverload::of(&QSpinBox::valueChanged), this, &ArmoryServersWidget::updateSaveButton); + + updateSaveButton(); + + // TODO: remove select server button if it's not required anymore + ui_->pushButtonSelectServer->hide(); + ui_->pushButtonKeyImport->hide(); +} + +void ArmoryServersWidget::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + // this check will prevent loop selectionChanged -> setupServerFromSelected -> select -> selectionChanged + if (deselected.isEmpty()) { + return; + } + + bool isEmpty = ui_->tableViewArmory->selectionModel()->selectedIndexes().isEmpty(); + ui_->pushButtonDeleteServer->setDisabled(isEmpty); + ui_->pushButtonEditServer->setDisabled(isEmpty); + ui_->pushButtonConnect->setDisabled(isEmpty); + ui_->pushButtonSelectServer->setDisabled(isEmpty); + + if (!isEmpty && selected.indexes().first().row() < ArmoryServersProvider::kDefaultServersCount) { + ui_->pushButtonDeleteServer->setDisabled(true); + ui_->pushButtonEditServer->setDisabled(true); + } + + resetForm(); + + // save to settings right after row highlight + setupServerFromSelected(true); +} + void ArmoryServersWidget::setRowSelected(int row) { QModelIndex currentIndex; - if (armoryServersProvider_->servers().size() >= 0) { + if (armoryServersProvider_ && (armoryServersProvider_->servers().size() >= 0)) { int indexOfCurrent = row; if (indexOfCurrent < 0 || indexOfCurrent >= armoryServersProvider_->servers().size()) { indexOfCurrent = 0; } currentIndex = armoryServersModel_->index(indexOfCurrent, 0); } + else { + if (row >= armoryServersModel_->rowCount()) { + row = 0; + } + currentIndex = armoryServersModel_->index(row, 0); + } ui_->tableViewArmory->selectionModel()->select(currentIndex , QItemSelectionModel::Select | QItemSelectionModel::Rows); } @@ -146,11 +204,17 @@ void ArmoryServersWidget::onAddServer() server.armoryDBPort = ui_->spinBoxPort->value(); server.armoryDBKey = ui_->lineEditKey->text(); - bool ok = armoryServersProvider_->add(server); - if (ok) { + if (armoryServersProvider_) { + bool ok = armoryServersProvider_->add(server); + if (ok) { + resetForm(); + setRowSelected(armoryServersProvider_->servers().size() - 1); + setupServerFromSelected(true); + } + } + else { + emit addServer(server); resetForm(); - setRowSelected(armoryServersProvider_->servers().size() - 1); - setupServerFromSelected(true); } } @@ -165,9 +229,13 @@ void ArmoryServersWidget::onDeleteServer() if (selectedRow < ArmoryServersProvider::kDefaultServersCount) { return; } - armoryServersProvider_->remove(selectedRow); - setRowSelected(0); - setupServerFromSelected(true); + if (armoryServersProvider_) { + armoryServersProvider_->remove(selectedRow); + setupServerFromSelected(true); + } + else { + emit delServer(selectedRow); + } } void ArmoryServersWidget::onEdit() @@ -177,11 +245,20 @@ void ArmoryServersWidget::onEdit() } int index = ui_->tableViewArmory->selectionModel()->selectedIndexes().first().row(); - if (index >= armoryServersProvider_->servers().size()) { - return; + ArmoryServer server; + if (armoryServersProvider_) { + if (index >= armoryServersProvider_->servers().size()) { + return; + } + server = armoryServersProvider_->servers().at(index); + } + else { + if (index >= servers_.size()) { + return; + } + server = servers_.at(index); //FIXME: use model instead to retrieve server data } - ArmoryServer server = armoryServersProvider_->servers().at(index); ui_->stackedWidgetAddSave->setCurrentWidget(ui_->pageSaveServerButton); ui_->lineEditName->setText(server.name); @@ -208,7 +285,7 @@ void ArmoryServersWidget::onSave() } int index = ui_->tableViewArmory->selectionModel()->selectedIndexes().first().row(); - if (index >= armoryServersProvider_->servers().size()) { + if (armoryServersProvider_ && (index >= armoryServersProvider_->servers().size())) { return; } @@ -219,10 +296,16 @@ void ArmoryServersWidget::onSave() server.armoryDBPort = ui_->spinBoxPort->value(); server.armoryDBKey = ui_->lineEditKey->text(); - bool ok = armoryServersProvider_->replace(index, server); - if (ok) { + if (armoryServersProvider_) { + bool ok = armoryServersProvider_->replace(index, server); + if (ok) { + resetForm(); + setRowSelected(armoryServersProvider_->indexOfCurrent()); + } + } + else { + emit updServer(index, server); resetForm(); - setRowSelected(armoryServersProvider_->indexOfCurrent()); } } @@ -251,14 +334,17 @@ void ArmoryServersWidget::setupServerFromSelected(bool needUpdate) if (ui_->tableViewArmory->selectionModel()->selectedIndexes().isEmpty()) { return; } - int index = ui_->tableViewArmory->selectionModel()->selectedIndexes().first().row(); - if (index >= armoryServersProvider_->servers().size()) { + if (armoryServersProvider_ && (index >= armoryServersProvider_->servers().size())) { return; } - - armoryServersProvider_->setupServer(index, needUpdate); - setRowSelected(armoryServersProvider_->indexOfCurrent()); + if (armoryServersProvider_) { + armoryServersProvider_->setupServer(index, needUpdate); + setRowSelected(armoryServersProvider_->indexOfCurrent()); + } + else { + emit setServer(index); + } } void ArmoryServersWidget::resetForm() @@ -269,7 +355,7 @@ void ArmoryServersWidget::resetForm() ui_->comboBoxNetworkType->setCurrentIndex(0); ui_->lineEditAddress->clear(); ui_->spinBoxPort->setValue(0); - ui_->spinBoxPort->setSpecialValueText(tr(" ")); + ui_->spinBoxPort->setSpecialValueText(QLatin1String(" ")); ui_->lineEditKey->clear(); } @@ -289,9 +375,24 @@ bool ArmoryServersWidget::isExpanded() const return isExpanded_; } +void ArmoryServersWidget::onArmoryServers(const QList& servers + , int idxCur, int idxConn) +{ + servers_ = servers; + if (idxCur < ArmoryServersProvider::kDefaultServersCount) { + ui_->pushButtonDeleteServer->setDisabled(true); + ui_->pushButtonEditServer->setDisabled(true); + } + + if (armoryServersModel_) { + armoryServersModel_->onArmoryServers(servers, idxCur, idxConn); + } + setRowSelected(idxCur); +} + void ArmoryServersWidget::onFormChanged() { - bool acceptable = ui_->lineEditAddress->hasAcceptableInput(); + const bool acceptable = ui_->lineEditAddress->hasAcceptableInput(); bool exists = false; bool valid = false; if (acceptable) { @@ -302,10 +403,33 @@ void ArmoryServersWidget::onFormChanged() armoryHost.armoryDBKey = ui_->lineEditKey->text(); valid = armoryHost.isValid(); if (valid) { - exists = armoryServersProvider_->indexOf(armoryHost.name) != -1 + if (armoryServersProvider_) { + exists = armoryServersProvider_->indexOf(armoryHost.name) != -1 || armoryServersProvider_->indexOf(armoryHost) != -1; + } + else { + for (int i = 0; i < servers_.size(); ++i) { + if ((armoryHost.name == servers_.at(i).name) + || ((armoryHost.armoryDBIp == servers_.at(i).armoryDBIp) && + (armoryHost.armoryDBPort == servers_.at(i).armoryDBPort))) { + exists = true; + break; + } + } + } } } ui_->pushButtonAddServer->setEnabled(valid && acceptable && !exists); ui_->pushButtonSaveServer->setEnabled(valid && acceptable); } + +void ArmoryServersWidget::onCurIndexChanged(int index) +{ + if (appSettings_) { + if (index == 1) { + ui_->spinBoxPort->setValue(appSettings_->GetDefaultArmoryRemotePort(NetworkType::MainNet)); + } else if (index == 2) { + ui_->spinBoxPort->setValue(appSettings_->GetDefaultArmoryRemotePort(NetworkType::TestNet)); + } + } +} diff --git a/BlockSettleUILib/Settings/ArmoryServersWidget.h b/BlockSettleUILib/Settings/ArmoryServersWidget.h index 2ae5741f5..f37071b4a 100644 --- a/BlockSettleUILib/Settings/ArmoryServersWidget.h +++ b/BlockSettleUILib/Settings/ArmoryServersWidget.h @@ -11,6 +11,7 @@ #ifndef ARMORYSERVERSWIDGET_H #define ARMORYSERVERSWIDGET_H +#include #include #include @@ -26,15 +27,18 @@ class ArmoryServersWidget : public QWidget Q_OBJECT public: - explicit ArmoryServersWidget(const std::shared_ptr& armoryServersProvider + [[deprecated]] explicit ArmoryServersWidget(const std::shared_ptr& armoryServersProvider , const std::shared_ptr &appSettings , QWidget *parent = nullptr); + explicit ArmoryServersWidget(QWidget* parent = nullptr); ~ArmoryServersWidget(); void setRowSelected(int row); bool isExpanded() const; + void onArmoryServers(const QList&, int idxCur, int idxConn); + public slots: void onAddServer(); void onDeleteServer(); @@ -46,9 +50,17 @@ public slots: void onExpandToggled(); void onFormChanged(); +private slots: + void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void onCurIndexChanged(int index); + signals: void reconnectArmory(); void needClose(); + void setServer(int); + void addServer(const ArmoryServer&); + void delServer(int); + void updServer(int, const ArmoryServer&); private: void setupServerFromSelected(bool needUpdate); @@ -62,7 +74,8 @@ private slots: std::shared_ptr armoryServersProvider_; std::shared_ptr appSettings_; - ArmoryServersViewModel *armoryServersModel_; + ArmoryServersViewModel* armoryServersModel_{ nullptr }; + QList servers_; bool isStartupDialog_ = false; bool isExpanded_ = true; }; diff --git a/BlockSettleUILib/Settings/ConfigDialog.cpp b/BlockSettleUILib/Settings/ConfigDialog.cpp index 01a540cfc..a6ebba26d 100644 --- a/BlockSettleUILib/Settings/ConfigDialog.cpp +++ b/BlockSettleUILib/Settings/ConfigDialog.cpp @@ -15,10 +15,9 @@ #include "GeneralSettingsPage.h" #include "NetworkSettingsPage.h" #include "SignersProvider.h" -#include "WalletSignerContainer.h" +#include "Wallets/WalletSignerContainer.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" -#include "autheid_utils.h" #include "ui_ConfigDialog.h" @@ -27,14 +26,14 @@ SettingsPage::SettingsPage(QWidget *parent) : QWidget(parent) -{ -} +{} void SettingsPage::init(const std::shared_ptr &appSettings , const std::shared_ptr &armoryServersProvider , const std::shared_ptr &signersProvider , const std::shared_ptr &signContainer - , const std::shared_ptr &walletsMgr) { + , const std::shared_ptr &walletsMgr) +{ appSettings_ = appSettings; armoryServersProvider_ = armoryServersProvider; signersProvider_ = signersProvider; @@ -44,6 +43,18 @@ void SettingsPage::init(const std::shared_ptr &appSettings display(); } +void SettingsPage::init(const ApplicationSettings::State& state) +{ + settings_ = state; + initSettings(); + display(); +} + +void SettingsPage::onSetting(int setting, const QVariant& value) +{ + settings_[static_cast(setting)] = value; +} + ConfigDialog::ConfigDialog(const std::shared_ptr& appSettings , const std::shared_ptr &armoryServersProvider @@ -71,7 +82,7 @@ ConfigDialog::ConfigDialog(const std::shared_ptr& appSettin for (const auto &page : pages_) { page->init(appSettings_, armoryServersProvider_, signersProvider_, signContainer_, walletsMgr); - connect(page, &SettingsPage::illformedSettings, this, &ConfigDialog::illformedSettings); + connect(page, &SettingsPage::illformedSettings, this, &ConfigDialog::onIllformedSettings); } ui_->listWidget->setCurrentRow(0); @@ -113,8 +124,66 @@ ConfigDialog::ConfigDialog(const std::shared_ptr& appSettin }); } +ConfigDialog::ConfigDialog(QWidget* parent) + : QDialog(parent) + , ui_(new Ui::ConfigDialog) +{ + ui_->setupUi(this); + + pages_ = { ui_->pageGeneral, ui_->pageNetwork, ui_->pageSigner, ui_->pageDealing + , ui_->pageAPI }; + + for (const auto& page : pages_) { + connect(page, &SettingsPage::illformedSettings, this, &ConfigDialog::onIllformedSettings); + connect(page, &SettingsPage::putSetting, this, &ConfigDialog::putSetting); + connect(page, &SettingsPage::resetSettings, this, &ConfigDialog::resetSettings); + } + connect(ui_->pageNetwork, &NetworkSettingsPage::reconnectArmory, this, &ConfigDialog::reconnectArmory); + connect(ui_->pageNetwork, &NetworkSettingsPage::setArmoryServer, this, &ConfigDialog::setArmoryServer); + connect(ui_->pageNetwork, &NetworkSettingsPage::addArmoryServer, this, &ConfigDialog::addArmoryServer); + connect(ui_->pageNetwork, &NetworkSettingsPage::delArmoryServer, this, &ConfigDialog::delArmoryServer); + connect(ui_->pageNetwork, &NetworkSettingsPage::updArmoryServer, this, &ConfigDialog::updArmoryServer); + connect(ui_->pageSigner, &SignerSettingsPage::setSigner, this, &ConfigDialog::setSigner); + + ui_->listWidget->setCurrentRow(0); + ui_->stackedWidget->setCurrentIndex(0); + + connect(ui_->listWidget, &QListWidget::currentRowChanged, this, &ConfigDialog::onSelectionChanged); + connect(ui_->pushButtonSetDefault, &QPushButton::clicked, this, &ConfigDialog::onDisplayDefault); + connect(ui_->pushButtonCancel, &QPushButton::clicked, this, &ConfigDialog::reject); + connect(ui_->pushButtonOk, &QPushButton::clicked, this, &ConfigDialog::onAcceptSettings); +} + ConfigDialog::~ConfigDialog() = default; +void ConfigDialog::onSettingsState(const ApplicationSettings::State& state) +{ + if (prevState_.empty()) { + prevState_ = state; + } + for (const auto& page : pages_) { + page->init(state); + } +} + +void ConfigDialog::onSetting(int setting, const QVariant& value) +{ + for (const auto& page : pages_) { + page->onSetting(setting, value); + } +} + +void ConfigDialog::onArmoryServers(const QList& servers, int idxCur, int idxConn) +{ + ui_->pageNetwork->onArmoryServers(servers, idxCur, idxConn); +} + +void ConfigDialog::onSignerSettings(const QList& signers + , const std::string& ownKey, int idxCur) +{ + ui_->pageSigner->onSignerSettings(signers, ownKey, idxCur); +} + void ConfigDialog::popupNetworkSettings() { ui_->stackedWidget->setCurrentWidget(ui_->pageNetwork); @@ -138,57 +207,14 @@ void ConfigDialog::encryptData(const std::shared_ptr & , const SecureBinaryData &data , const ConfigDialog::EncryptCb &cb) { - getChatPrivKey(walletsMgr, signContainer, [data, cb](EncryptError error, const SecureBinaryData &privKey) { - if (error != EncryptError::NoError) { - cb(error, {}); - return; - } - auto pubKey = autheid::getPublicKey(autheid::PrivateKey(privKey.getPtr(), privKey.getPtr() + privKey.getSize())); - auto encrypted = autheid::encryptData(data.getPtr(), data.getSize(), pubKey); - if (encrypted.empty()) { - cb(EncryptError::EncryptError, {}); - return; - } - cb(EncryptError::NoError, SecureBinaryData(encrypted.data(), encrypted.size())); - }); + cb(EncryptError::NoEncryptionKey, {}); } void ConfigDialog::decryptData(const std::shared_ptr &walletsMgr , const std::shared_ptr &signContainer , const SecureBinaryData &data, const ConfigDialog::EncryptCb &cb) { - getChatPrivKey(walletsMgr, signContainer, [data, cb](EncryptError error, const SecureBinaryData &privKey) { - if (error != EncryptError::NoError) { - cb(error, {}); - return; - } - auto privKeyCopy = autheid::PrivateKey(privKey.getPtr(), privKey.getPtr() + privKey.getSize()); - auto decrypted = autheid::decryptData(data.getPtr(), data.getSize(), privKeyCopy); - if (decrypted.empty()) { - cb(EncryptError::EncryptError, {}); - return; - } - cb(EncryptError::NoError, SecureBinaryData(decrypted.data(), decrypted.size())); - }); -} - -void ConfigDialog::getChatPrivKey(const std::shared_ptr &walletsMgr - , const std::shared_ptr &signContainer - , const ConfigDialog::EncryptCb &cb) -{ - const auto &primaryWallet = walletsMgr->getPrimaryWallet(); - if (!primaryWallet) { - cb(EncryptError::NoPrimaryWallet, {}); - return; - } - auto walletSigner = std::dynamic_pointer_cast(signContainer); - walletSigner->getChatNode(primaryWallet->walletId(), [cb](const BIP32_Node &node) { - if (node.getPrivateKey().empty()) { - cb(EncryptError::NoEncryptionKey, {}); - return; - } - cb(EncryptError::NoError, node.getPrivateKey()); - }); + cb(EncryptError::NoEncryptionKey, {}); } void ConfigDialog::onDisplayDefault() @@ -201,8 +227,9 @@ void ConfigDialog::onAcceptSettings() for (const auto &page : pages_) { page->apply(); } - - appSettings_->SaveSettings(); + if (appSettings_) { + appSettings_->SaveSettings(); + } accept(); } @@ -211,13 +238,18 @@ void ConfigDialog::onSelectionChanged(int currentRow) ui_->stackedWidget->setCurrentIndex(currentRow); } -void ConfigDialog::illformedSettings(bool illformed) +void ConfigDialog::onIllformedSettings(bool illformed) { ui_->pushButtonOk->setEnabled(!illformed); } void ConfigDialog::reject() { - appSettings_->setState(prevState_); + if (appSettings_) { + appSettings_->setState(prevState_); + } + else { + emit resetSettingsToState(prevState_); + } QDialog::reject(); } diff --git a/BlockSettleUILib/Settings/ConfigDialog.h b/BlockSettleUILib/Settings/ConfigDialog.h index 32e2c1a7b..a66cff364 100644 --- a/BlockSettleUILib/Settings/ConfigDialog.h +++ b/BlockSettleUILib/Settings/ConfigDialog.h @@ -15,7 +15,8 @@ #include #include "ApplicationSettings.h" #include "ArmoryServersProvider.h" -#include "SignContainer.h" +#include "Settings/SignersProvider.h" +#include "Wallets/SignContainer.h" class ArmoryServersProvider; class SignersProvider; @@ -31,11 +32,14 @@ class SettingsPage : public QWidget public: SettingsPage(QWidget *parent); - virtual void init(const std::shared_ptr &appSettings + [[deprecated]] virtual void init(const std::shared_ptr &appSettings , const std::shared_ptr &armoryServersProvider , const std::shared_ptr &signersProvider , const std::shared_ptr &signContainer , const std::shared_ptr &walletsMgr); + virtual void init(const ApplicationSettings::State &); + + virtual void onSetting(int setting, const QVariant& value); public slots: virtual void initSettings() {} @@ -44,9 +48,12 @@ public slots: virtual void apply() = 0; signals: + void putSetting(ApplicationSettings::Setting, const QVariant&); + void resetSettings(const std::vector&); void illformedSettings(bool illformed); protected: + ApplicationSettings::State settings_; std::shared_ptr appSettings_; std::shared_ptr armoryServersProvider_; std::shared_ptr signersProvider_; @@ -60,16 +67,22 @@ class ConfigDialog : public QDialog Q_OBJECT public: - ConfigDialog(const std::shared_ptr& appSettings + [[deprecated]] ConfigDialog(const std::shared_ptr& appSettings , const std::shared_ptr &armoryServersProvider , const std::shared_ptr &signersProvider , const std::shared_ptr &signContainer , const std::shared_ptr &walletsMgr , QWidget* parent = nullptr); + ConfigDialog(QWidget* parent = nullptr); ~ConfigDialog() override; void popupNetworkSettings(); + void onSettingsState(const ApplicationSettings::State &); + void onSetting(int setting, const QVariant& value); + void onArmoryServers(const QList&, int idxCur, int idxConn); + void onSignerSettings(const QList&, const std::string& ownKey, int idxCur); + enum class EncryptError { NoError, @@ -91,15 +104,20 @@ private slots: void onDisplayDefault(); void onAcceptSettings(); void onSelectionChanged(int currentRow); - void illformedSettings(bool illformed); + void onIllformedSettings(bool illformed); signals: void reconnectArmory(); + void putSetting(ApplicationSettings::Setting, const QVariant&); + void resetSettings(const std::vector&); + void resetSettingsToState(const ApplicationSettings::State &); + void setArmoryServer(int); + void addArmoryServer(const ArmoryServer&); + void delArmoryServer(int); + void updArmoryServer(int, const ArmoryServer&); + void setSigner(int); private: - static void getChatPrivKey(const std::shared_ptr &walletsMgr - , const std::shared_ptr &signContainer, const EncryptCb &cb); - std::unique_ptr ui_; std::shared_ptr appSettings_; std::shared_ptr armoryServersProvider_; diff --git a/BlockSettleUILib/Settings/ConfigDialog.ui b/BlockSettleUILib/Settings/ConfigDialog.ui index e11962c21..8f6bdeec2 100644 --- a/BlockSettleUILib/Settings/ConfigDialog.ui +++ b/BlockSettleUILib/Settings/ConfigDialog.ui @@ -1,4 +1,14 @@ + ConfigDialog diff --git a/BlockSettleUILib/Settings/DealingSettingsPage.cpp b/BlockSettleUILib/Settings/DealingSettingsPage.cpp index 2839d3a2a..4df06aacf 100644 --- a/BlockSettleUILib/Settings/DealingSettingsPage.cpp +++ b/BlockSettleUILib/Settings/DealingSettingsPage.cpp @@ -106,45 +106,92 @@ static inline int priceUpdateTimeout(int index) } } +void DealingSettingsPage::init(const ApplicationSettings::State& state) +{ + if (state.find(ApplicationSettings::dropQN) == state.end()) { + return; // not our snapshot + } + SettingsPage::init(state); +} + void DealingSettingsPage::display() { - ui_->checkBoxDrop->setChecked(appSettings_->get(ApplicationSettings::dropQN)); - ui_->showQuoted->setChecked(appSettings_->get(ApplicationSettings::ShowQuoted)); - ui_->fx->setCurrentIndex(limitIndex(appSettings_->get(ApplicationSettings::FxRfqLimit))); - ui_->xbt->setCurrentIndex(limitIndex(appSettings_->get(ApplicationSettings::XbtRfqLimit))); - ui_->pm->setCurrentIndex(limitIndex(appSettings_->get(ApplicationSettings::PmRfqLimit))); - ui_->disableBlueDot->setChecked(appSettings_->get( - ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter)); - ui_->priceUpdateTimeout->setCurrentIndex(priceUpdateIndex(appSettings_->get( - ApplicationSettings::PriceUpdateInterval))); + if (appSettings_) { + ui_->checkBoxDrop->setChecked(appSettings_->get(ApplicationSettings::dropQN)); + ui_->showQuoted->setChecked(appSettings_->get(ApplicationSettings::ShowQuoted)); + ui_->fx->setCurrentIndex(limitIndex(appSettings_->get(ApplicationSettings::FxRfqLimit))); + ui_->xbt->setCurrentIndex(limitIndex(appSettings_->get(ApplicationSettings::XbtRfqLimit))); + ui_->pm->setCurrentIndex(limitIndex(appSettings_->get(ApplicationSettings::PmRfqLimit))); + ui_->disableBlueDot->setChecked(appSettings_->get( + ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter)); + ui_->priceUpdateTimeout->setCurrentIndex(priceUpdateIndex(appSettings_->get( + ApplicationSettings::PriceUpdateInterval))); + } + else { + ui_->checkBoxDrop->setChecked(settings_.at(ApplicationSettings::dropQN).toBool()); + ui_->showQuoted->setChecked(settings_.at(ApplicationSettings::ShowQuoted).toBool()); + ui_->fx->setCurrentIndex(limitIndex(settings_.at(ApplicationSettings::FxRfqLimit).toInt())); + ui_->xbt->setCurrentIndex(limitIndex(settings_.at(ApplicationSettings::XbtRfqLimit).toInt())); + ui_->pm->setCurrentIndex(limitIndex(settings_.at(ApplicationSettings::PmRfqLimit).toInt())); + ui_->disableBlueDot->setChecked(settings_.at(ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter).toBool()); + ui_->priceUpdateTimeout->setCurrentIndex(priceUpdateIndex(settings_.at( + ApplicationSettings::PriceUpdateInterval).toInt())); + } } void DealingSettingsPage::reset() { - for (const auto &setting : {ApplicationSettings::dropQN, ApplicationSettings::ShowQuoted + const std::vector resetList{ + ApplicationSettings::dropQN, ApplicationSettings::ShowQuoted , ApplicationSettings::FxRfqLimit, ApplicationSettings::XbtRfqLimit - , ApplicationSettings::PmRfqLimit, ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter - , ApplicationSettings::PriceUpdateInterval}) { - appSettings_->reset(setting, false); + , ApplicationSettings::PmRfqLimit + , ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter + , ApplicationSettings::PriceUpdateInterval + }; + if (appSettings_) { + for (const auto& setting : resetList) { + appSettings_->reset(setting, false); + } + display(); + } + else { + emit resetSettings(resetList); } - display(); } void DealingSettingsPage::apply() { - appSettings_->set(ApplicationSettings::dropQN, ui_->checkBoxDrop->isChecked()); - appSettings_->set(ApplicationSettings::ShowQuoted, ui_->showQuoted->isChecked()); - appSettings_->set(ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter, - ui_->disableBlueDot->isChecked()); - appSettings_->set(ApplicationSettings::FxRfqLimit, limit(ui_->fx->currentIndex())); - appSettings_->set(ApplicationSettings::XbtRfqLimit, limit(ui_->xbt->currentIndex())); - appSettings_->set(ApplicationSettings::PmRfqLimit, limit(ui_->pm->currentIndex())); - appSettings_->set(ApplicationSettings::PriceUpdateInterval, priceUpdateTimeout( - ui_->priceUpdateTimeout->currentIndex())); + if (appSettings_) { + appSettings_->set(ApplicationSettings::dropQN, ui_->checkBoxDrop->isChecked()); + appSettings_->set(ApplicationSettings::ShowQuoted, ui_->showQuoted->isChecked()); + appSettings_->set(ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter, + ui_->disableBlueDot->isChecked()); + appSettings_->set(ApplicationSettings::FxRfqLimit, limit(ui_->fx->currentIndex())); + appSettings_->set(ApplicationSettings::XbtRfqLimit, limit(ui_->xbt->currentIndex())); + appSettings_->set(ApplicationSettings::PmRfqLimit, limit(ui_->pm->currentIndex())); + appSettings_->set(ApplicationSettings::PriceUpdateInterval, priceUpdateTimeout( + ui_->priceUpdateTimeout->currentIndex())); + } + else { + emit putSetting(ApplicationSettings::dropQN, ui_->checkBoxDrop->isChecked()); + emit putSetting(ApplicationSettings::ShowQuoted, ui_->showQuoted->isChecked()); + emit putSetting(ApplicationSettings::DisableBlueDotOnTabOfRfqBlotter, + ui_->disableBlueDot->isChecked()); + emit putSetting(ApplicationSettings::FxRfqLimit, limit(ui_->fx->currentIndex())); + emit putSetting(ApplicationSettings::XbtRfqLimit, limit(ui_->xbt->currentIndex())); + emit putSetting(ApplicationSettings::PmRfqLimit, limit(ui_->pm->currentIndex())); + emit putSetting(ApplicationSettings::PriceUpdateInterval, priceUpdateTimeout( + ui_->priceUpdateTimeout->currentIndex())); + } } void DealingSettingsPage::onResetCounters() { - appSettings_->reset(ApplicationSettings::Filter_MD_QN_cnt); + if (appSettings_) { + appSettings_->reset(ApplicationSettings::Filter_MD_QN_cnt); + } + else { + emit resetSettings({ ApplicationSettings::Filter_MD_QN_cnt }); + } ui_->pushButtonResetCnt->setEnabled(false); } diff --git a/BlockSettleUILib/Settings/DealingSettingsPage.h b/BlockSettleUILib/Settings/DealingSettingsPage.h index 088688d7f..228a023b8 100644 --- a/BlockSettleUILib/Settings/DealingSettingsPage.h +++ b/BlockSettleUILib/Settings/DealingSettingsPage.h @@ -28,6 +28,7 @@ class DealingSettingsPage : public SettingsPage DealingSettingsPage(QWidget* parent = nullptr); ~DealingSettingsPage() override; + void init(const ApplicationSettings::State&) override; void display() override; void reset() override; void apply() override; diff --git a/BlockSettleUILib/Settings/GeneralSettingsPage.cpp b/BlockSettleUILib/Settings/GeneralSettingsPage.cpp index 939167c9d..39ed38ec0 100644 --- a/BlockSettleUILib/Settings/GeneralSettingsPage.cpp +++ b/BlockSettleUILib/Settings/GeneralSettingsPage.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -49,125 +49,211 @@ GeneralSettingsPage::GeneralSettingsPage(QWidget* parent) GeneralSettingsPage::~GeneralSettingsPage() = default; -void GeneralSettingsPage::display() +void GeneralSettingsPage::init(const ApplicationSettings::State& state) { - ui_->checkBoxLaunchToTray->setChecked(appSettings_->get(ApplicationSettings::launchToTray)); - ui_->checkBoxMinimizeToTray->setChecked(appSettings_->get(ApplicationSettings::minimizeToTray)); - ui_->checkBoxCloseToTray->setChecked(appSettings_->get(ApplicationSettings::closeToTray)); - ui_->checkBoxShowTxNotification->setChecked(appSettings_->get(ApplicationSettings::notifyOnTX)); - ui_->addvancedDialogByDefaultCheckBox->setChecked(appSettings_->get(ApplicationSettings::AdvancedTxDialogByDefault)); - ui_->subscribeToMdOnStartCheckBox->setChecked(appSettings_->get(ApplicationSettings::SubscribeToMDOnStart)); - ui_->detailedSettlementTxDialogByDefaultCheckBox->setChecked( - appSettings_->get(ApplicationSettings::DetailedSettlementTxDialogByDefault)); - - // DetailedSettlementTxDialogByDefault - - const auto cfg = appSettings_->GetLogsConfig(); - ui_->logFileName->setText(QString::fromStdString(cfg.at(0).fileName)); - ui_->logMsgFileName->setText(QString::fromStdString(cfg.at(1).fileName)); - ui_->logLevel->setCurrentIndex(static_cast(cfg.at(0).level)); - ui_->logLevelMsg->setCurrentIndex(static_cast(cfg.at(1).level)); - - ui_->warnLabel->hide(); - - UiUtils::fillHDWalletsComboBox(ui_->comboBox_defaultWallet, walletsMgr_, UiUtils::WalletsTypes::All); + if (state.find(ApplicationSettings::launchToTray) == state.end()) { + return; // not our snapshot + } + SettingsPage::init(state); +} - auto walletId = appSettings_->getDefaultWalletId(); - bool setFirstWalletAsDefault = false; - if (!walletId.empty()) { - int selectedIndex = UiUtils::selectWalletInCombobox(ui_->comboBox_defaultWallet, walletId, UiUtils::WalletsTypes::All); - if (selectedIndex == -1) { +void GeneralSettingsPage::display() +{ + if (appSettings_ && walletsMgr_) { + ui_->checkBoxLaunchToTray->setChecked(appSettings_->get(ApplicationSettings::launchToTray)); + ui_->checkBoxMinimizeToTray->setChecked(appSettings_->get(ApplicationSettings::minimizeToTray)); + ui_->checkBoxCloseToTray->setChecked(appSettings_->get(ApplicationSettings::closeToTray)); + ui_->checkBoxShowTxNotification->setChecked(appSettings_->get(ApplicationSettings::notifyOnTX)); + ui_->addvancedDialogByDefaultCheckBox->setChecked(appSettings_->get(ApplicationSettings::AdvancedTxDialogByDefault)); + ui_->subscribeToMdOnStartCheckBox->setChecked(appSettings_->get(ApplicationSettings::SubscribeToMDOnStart)); + ui_->detailedSettlementTxDialogByDefaultCheckBox->setChecked( + appSettings_->get(ApplicationSettings::DetailedSettlementTxDialogByDefault)); + + // DetailedSettlementTxDialogByDefault + const auto cfg = appSettings_->GetLogsConfig(); + ui_->logFileName->setText(QString::fromStdString(cfg.at(0).fileName)); + ui_->logMsgFileName->setText(QString::fromStdString(cfg.at(1).fileName)); + ui_->logLevel->setCurrentIndex(static_cast(cfg.at(0).level)); + ui_->logLevelMsg->setCurrentIndex(static_cast(cfg.at(1).level)); + + UiUtils::fillHDWalletsComboBox(ui_->comboBox_defaultWallet, walletsMgr_, UiUtils::WalletsTypes::All); + + auto walletId = appSettings_->getDefaultWalletId(); + bool setFirstWalletAsDefault = false; + if (!walletId.empty()) { + int selectedIndex = UiUtils::selectWalletInCombobox(ui_->comboBox_defaultWallet, walletId, UiUtils::WalletsTypes::All); + if (selectedIndex == -1) { + setFirstWalletAsDefault = true; + } + } else { setFirstWalletAsDefault = true; } - } else { - setFirstWalletAsDefault = true; - } - if (setFirstWalletAsDefault) { - walletId = UiUtils::getSelectedWalletId(ui_->comboBox_defaultWallet); - appSettings_->setDefaultWalletId(walletId); + if (setFirstWalletAsDefault) { + walletId = UiUtils::getSelectedWalletId(ui_->comboBox_defaultWallet, ui_->comboBox_defaultWallet->currentIndex()); + appSettings_->setDefaultWalletId(walletId); + } + } + else { + ui_->checkBoxLaunchToTray->setChecked(settings_.at(ApplicationSettings::launchToTray).toBool()); + ui_->checkBoxMinimizeToTray->setChecked(settings_.at(ApplicationSettings::minimizeToTray).toBool()); + ui_->checkBoxCloseToTray->setChecked(settings_.at(ApplicationSettings::closeToTray).toBool()); + ui_->checkBoxShowTxNotification->setChecked(settings_.at(ApplicationSettings::notifyOnTX).toBool()); + ui_->addvancedDialogByDefaultCheckBox->setChecked(settings_.at(ApplicationSettings::AdvancedTxDialogByDefault).toBool()); + ui_->subscribeToMdOnStartCheckBox->setChecked(settings_.at(ApplicationSettings::SubscribeToMDOnStart).toBool()); + ui_->detailedSettlementTxDialogByDefaultCheckBox->setChecked( + settings_.at(ApplicationSettings::DetailedSettlementTxDialogByDefault).toBool()); + + const auto cfgLog = ApplicationSettings::parseLogConfig(settings_.at( + ApplicationSettings::logDefault).toStringList()); + const auto cfgMessages = ApplicationSettings::parseLogConfig(settings_.at( + ApplicationSettings::logMessages).toStringList()); + ui_->logFileName->setText(QString::fromStdString(cfgLog.fileName)); + ui_->logMsgFileName->setText(QString::fromStdString(cfgMessages.fileName)); + ui_->logLevel->setCurrentIndex(static_cast(cfgLog.level)); + ui_->logLevelMsg->setCurrentIndex(static_cast(cfgMessages.level)); + + //TODO: handle default wallet if needed } + ui_->warnLabel->hide(); } void GeneralSettingsPage::reset() { - for (const auto &setting : {ApplicationSettings::launchToTray, ApplicationSettings::minimizeToTray + const std::vector resetList = { + ApplicationSettings::launchToTray, ApplicationSettings::minimizeToTray , ApplicationSettings::closeToTray, ApplicationSettings::notifyOnTX - , ApplicationSettings::AdvancedTxDialogByDefault, ApplicationSettings::SubscribeToMDOnStart - , ApplicationSettings::logDefault, ApplicationSettings::logMessages}) { - appSettings_->reset(setting, false); + , ApplicationSettings::AdvancedTxDialogByDefault + , ApplicationSettings::SubscribeToMDOnStart + , ApplicationSettings::logDefault, ApplicationSettings::logMessages + }; + if (appSettings_) { + for (const auto& setting : resetList) { + appSettings_->reset(setting, false); + } + display(); + } + else { + emit resetSettings(resetList); } - display(); } static inline QString logLevel(int level) { switch(level) { - case 0 : return QLatin1String("trace"); - case 1 : return QLatin1String("debug"); - case 2 : return QLatin1String("info"); - case 3 : return QLatin1String("warn"); - case 4 : return QLatin1String("error"); - case 5 : return QLatin1String("crit"); - default : return QString(); + case 0 : return QObject::tr("trace"); + case 1 : return QObject::tr("debug"); + case 2 : return QObject::tr("info"); + case 3 : return QObject::tr("warn"); + case 4 : return QObject::tr("error"); + case 5 : return QObject::tr("crit"); + default : return QString(); } } void GeneralSettingsPage::apply() { - appSettings_->set(ApplicationSettings::launchToTray, ui_->checkBoxLaunchToTray->isChecked()); - appSettings_->set(ApplicationSettings::minimizeToTray, ui_->checkBoxMinimizeToTray->isChecked()); - appSettings_->set(ApplicationSettings::closeToTray, ui_->checkBoxCloseToTray->isChecked()); - appSettings_->set(ApplicationSettings::notifyOnTX, ui_->checkBoxShowTxNotification->isChecked()); - appSettings_->set(ApplicationSettings::AdvancedTxDialogByDefault, - ui_->addvancedDialogByDefaultCheckBox->isChecked()); + const auto walletId = UiUtils::getSelectedWalletId(ui_->comboBox_defaultWallet, ui_->comboBox_defaultWallet->currentIndex()); - appSettings_->set(ApplicationSettings::SubscribeToMDOnStart - , ui_->subscribeToMdOnStartCheckBox->isChecked()); + if (appSettings_) { + appSettings_->set(ApplicationSettings::launchToTray, ui_->checkBoxLaunchToTray->isChecked()); + appSettings_->set(ApplicationSettings::minimizeToTray, ui_->checkBoxMinimizeToTray->isChecked()); + appSettings_->set(ApplicationSettings::closeToTray, ui_->checkBoxCloseToTray->isChecked()); + appSettings_->set(ApplicationSettings::notifyOnTX, ui_->checkBoxShowTxNotification->isChecked()); + appSettings_->set(ApplicationSettings::AdvancedTxDialogByDefault, + ui_->addvancedDialogByDefaultCheckBox->isChecked()); - appSettings_->set(ApplicationSettings::DetailedSettlementTxDialogByDefault - , ui_->detailedSettlementTxDialogByDefaultCheckBox->isChecked()); + appSettings_->set(ApplicationSettings::SubscribeToMDOnStart + , ui_->subscribeToMdOnStartCheckBox->isChecked()); - auto cfg = appSettings_->GetLogsConfig(); + appSettings_->set(ApplicationSettings::DetailedSettlementTxDialogByDefault + , ui_->detailedSettlementTxDialogByDefaultCheckBox->isChecked()); - { - QStringList logSettings; - logSettings << ui_->logFileName->text(); - logSettings << QString::fromStdString(cfg.at(0).category); - logSettings << QString::fromStdString(cfg.at(0).pattern); + auto cfg = appSettings_->GetLogsConfig(); - if (ui_->logLevel->currentIndex() < static_cast(bs::LogLevel::off)) { - logSettings << logLevel(ui_->logLevel->currentIndex()); - } else { - logSettings << QString(); + { + QStringList logSettings; + logSettings << ui_->logFileName->text(); + logSettings << QString::fromStdString(cfg.at(0).category); + logSettings << QString::fromStdString(cfg.at(0).pattern); + + if (ui_->logLevel->currentIndex() < static_cast(bs::LogLevel::off)) { + logSettings << logLevel(ui_->logLevel->currentIndex()); + } else { + logSettings << QString(); + } + + appSettings_->set(ApplicationSettings::logDefault, logSettings); } - appSettings_->set(ApplicationSettings::logDefault, logSettings); - } + { + QStringList logSettings; + logSettings << ui_->logMsgFileName->text(); + logSettings << QString::fromStdString(cfg.at(1).category); + logSettings << QString::fromStdString(cfg.at(1).pattern); - { - QStringList logSettings; - logSettings << ui_->logMsgFileName->text(); - logSettings << QString::fromStdString(cfg.at(1).category); - logSettings << QString::fromStdString(cfg.at(1).pattern); + if (ui_->logLevelMsg->currentIndex() < static_cast(bs::LogLevel::off)) { + logSettings << logLevel(ui_->logLevelMsg->currentIndex()); + } else { + logSettings << QString(); + } - if (ui_->logLevelMsg->currentIndex() < static_cast(bs::LogLevel::off)) { - logSettings << logLevel(ui_->logLevelMsg->currentIndex()); - } else { - logSettings << QString(); + appSettings_->set(ApplicationSettings::logMessages, logSettings); } - - appSettings_->set(ApplicationSettings::logMessages, logSettings); + appSettings_->setDefaultWalletId(walletId); } + else { // don't update local settings_ yet - the update will arrive explicitly + emit putSetting(ApplicationSettings::launchToTray, ui_->checkBoxLaunchToTray->isChecked()); + emit putSetting(ApplicationSettings::minimizeToTray, ui_->checkBoxMinimizeToTray->isChecked()); + emit putSetting(ApplicationSettings::closeToTray, ui_->checkBoxCloseToTray->isChecked()); + emit putSetting(ApplicationSettings::notifyOnTX, ui_->checkBoxShowTxNotification->isChecked()); + emit putSetting(ApplicationSettings::AdvancedTxDialogByDefault, ui_->addvancedDialogByDefaultCheckBox->isChecked()); + emit putSetting(ApplicationSettings::SubscribeToMDOnStart, ui_->subscribeToMdOnStartCheckBox->isChecked()); + emit putSetting(ApplicationSettings::DetailedSettlementTxDialogByDefault + , ui_->detailedSettlementTxDialogByDefaultCheckBox->isChecked()); + + const auto netType = static_cast(settings_.at(ApplicationSettings::netType).toInt()); + emit putSetting((netType == NetworkType::TestNet) ? ApplicationSettings::DefaultXBTTradeWalletIdTestnet + : ApplicationSettings::DefaultXBTTradeWalletIdMainnet, QString::fromStdString(walletId)); + + const auto cfgLog = ApplicationSettings::parseLogConfig(settings_.at( + ApplicationSettings::logDefault).toStringList()); + { + QStringList logSettings; + logSettings << ui_->logFileName->text(); + logSettings << QString::fromStdString(cfgLog.category); + logSettings << QString::fromStdString(cfgLog.pattern); + + if (ui_->logLevel->currentIndex() < static_cast(bs::LogLevel::off)) { + logSettings << logLevel(ui_->logLevel->currentIndex()); + } else { + logSettings << QString(); + } + emit putSetting(ApplicationSettings::logDefault, logSettings); + } - const auto walletId = UiUtils::getSelectedWalletId(ui_->comboBox_defaultWallet); - appSettings_->setDefaultWalletId(walletId); + const auto cfgMessages = ApplicationSettings::parseLogConfig(settings_.at( + ApplicationSettings::logMessages).toStringList()); + { + QStringList logSettings; + logSettings << ui_->logMsgFileName->text(); + logSettings << QString::fromStdString(cfgMessages.category); + logSettings << QString::fromStdString(cfgMessages.pattern); + + if (ui_->logLevelMsg->currentIndex() < static_cast(bs::LogLevel::off)) { + logSettings << logLevel(ui_->logLevelMsg->currentIndex()); + } else { + logSettings << QString(); + } + emit putSetting(ApplicationSettings::logMessages, logSettings); + } + } } void GeneralSettingsPage::onSelectLogFile() { QString fileName = QFileDialog::getSaveFileName(this, - tr("Select file for General Terminal logs..."), + tr("Select file for General Terminal logs"), QFileInfo(ui_->logFileName->text()).path(), QString(), nullptr, QFileDialog::DontConfirmOverwrite); @@ -179,7 +265,7 @@ void GeneralSettingsPage::onSelectLogFile() void GeneralSettingsPage::onSelectMsgLogFile() { QString fileName = QFileDialog::getSaveFileName(this, - tr("Select file for Matching Engine logs..."), + tr("Select file for Matching Engine logs"), QFileInfo(ui_->logMsgFileName->text()).path(), QString(), nullptr, QFileDialog::DontConfirmOverwrite); @@ -190,49 +276,41 @@ void GeneralSettingsPage::onSelectMsgLogFile() void GeneralSettingsPage::onLogFileNameEdited(const QString &) { - checkSettings(); + checkLogSettings(); } void GeneralSettingsPage::onLogLevelChanged(int) { - checkSettings(); + checkLogSettings(); } -void GeneralSettingsPage::checkSettings() +void GeneralSettingsPage::checkLogSettings() { ui_->warnLabel->show(); if (ui_->groupBoxLogging->isChecked()) { if (ui_->logFileName->text().isEmpty()) { - ui_->warnLabel->setText(tr("Log files must be named.")); - + ui_->warnLabel->setText(tr("Log files must be named")); emit illformedSettings(true); - return; } } if (ui_->groupBoxLoggingMsg->isChecked()) { if (ui_->logMsgFileName->text().isEmpty()) { - ui_->warnLabel->setText(tr("Log files must be named.")); - + ui_->warnLabel->setText(tr("Log files must be named")); emit illformedSettings(true); - return; } } if (ui_->groupBoxLogging->isChecked() && ui_->groupBoxLoggingMsg->isChecked()) { if (ui_->logFileName->text() == ui_->logMsgFileName->text()) { - ui_->warnLabel->setText(tr("Logging requires multiple files.")); - + ui_->warnLabel->setText(tr("Logging requires multiple files")); emit illformedSettings(true); - return; } } - - ui_->warnLabel->setText(tr("Changes will take effect after the application is restarted.")); - + ui_->warnLabel->setText(tr("Changes will take effect after the application is restarted")); emit illformedSettings(false); } diff --git a/BlockSettleUILib/Settings/GeneralSettingsPage.h b/BlockSettleUILib/Settings/GeneralSettingsPage.h index 92baa3a6c..0121e9839 100644 --- a/BlockSettleUILib/Settings/GeneralSettingsPage.h +++ b/BlockSettleUILib/Settings/GeneralSettingsPage.h @@ -28,6 +28,7 @@ class GeneralSettingsPage : public SettingsPage GeneralSettingsPage(QWidget* parent = nullptr); ~GeneralSettingsPage() override; + void init(const ApplicationSettings::State&) override; void display() override; void reset() override; void apply() override; @@ -39,7 +40,7 @@ private slots: void onLogLevelChanged(int); private: - void checkSettings(); + void checkLogSettings(); signals: void requestDataEncryption(); diff --git a/BlockSettleUILib/Settings/HeadlessSettings.cpp b/BlockSettleUILib/Settings/HeadlessSettings.cpp index 374f3e350..4b5460a56 100644 --- a/BlockSettleUILib/Settings/HeadlessSettings.cpp +++ b/BlockSettleUILib/Settings/HeadlessSettings.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -10,9 +10,9 @@ */ #include #include - +#include "ArmoryConfig.h" #include "BIP150_151.h" -#include "BlockDataManagerConfig.h" +#include "BTCNumericTypes.h" #include "BtcUtils.h" #include "cxxopts.hpp" #include "HeadlessSettings.h" @@ -61,7 +61,6 @@ bool HeadlessSettings::loadSettings(int argc, char **argv) std::string walletsDir; cxxopts::Options options("BlockSettle Signer", "Headless Signer process"); - std::string guiMode; options.add_options() ("h,help", "Print help") ("a,listen", "IP address to listen on" @@ -80,8 +79,6 @@ bool HeadlessSettings::loadSettings(int argc, char **argv) , cxxopts::value()->default_value("true")) ("auto_sign_spend_limit", "Spend limit expressed in XBT for auto-sign operations" , cxxopts::value(autoSignSpendLimit)) - ("g,guimode", "GUI run mode" - , cxxopts::value(guiMode)->default_value("fullgui")) ; try { @@ -130,20 +127,10 @@ bool HeadlessSettings::loadSettings(int argc, char **argv) exit(0); } - if (guiMode == "litegui") { - runMode_ = bs::signer::RunMode::litegui; - } - else if (guiMode == "fullgui") { - runMode_ = bs::signer::RunMode::fullgui; - } - else if (guiMode == "headless") { - runMode_ = bs::signer::RunMode::headless; - } - if (testNet()) { - NetworkConfig::selectNetwork(NETWORK_MODE_TESTNET); + Armory::Config::NetworkSettings::selectNetwork(Armory::Config::NETWORK_MODE_TESTNET); } else { - NetworkConfig::selectNetwork(NETWORK_MODE_MAINNET); + Armory::Config::NetworkSettings::selectNetwork(Armory::Config::NETWORK_MODE_MAINNET); } return true; } diff --git a/BlockSettleUILib/Settings/HeadlessSettings.h b/BlockSettleUILib/Settings/HeadlessSettings.h index b41e67582..a4bfc5325 100644 --- a/BlockSettleUILib/Settings/HeadlessSettings.h +++ b/BlockSettleUILib/Settings/HeadlessSettings.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,7 +13,7 @@ #include #include "BtcDefinitions.h" -#include "SignerDefs.h" +#include "Wallets/SignerDefs.h" #include namespace spdlog { @@ -49,8 +49,6 @@ class HeadlessSettings bool twoWaySignerAuth() const; bool offline() const; - bs::signer::RunMode runMode() const { return runMode_; } - BinaryData serverIdKey() const { return serverIdKey_; } void setServerIdKey(const BinaryData &key) { serverIdKey_ = key; } @@ -67,7 +65,6 @@ class HeadlessSettings std::string logFile_; std::string termIDKeyStr_; - bs::signer::RunMode runMode_; std::string walletsDir_; std::unique_ptr d_; BinaryData serverIdKey_; diff --git a/BlockSettleUILib/Settings/NetworkSettingsPage.cpp b/BlockSettleUILib/Settings/NetworkSettingsPage.cpp index 0d7f3a57c..f8fe5d9c1 100644 --- a/BlockSettleUILib/Settings/NetworkSettingsPage.cpp +++ b/BlockSettleUILib/Settings/NetworkSettingsPage.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -19,7 +19,7 @@ #include "ApplicationSettings.h" #include "ArmoryServersWidget.h" #include "WebSocketClient.h" -#include "HeadlessContainer.h" +#include "Wallets/HeadlessContainer.h" #include "ArmoryServersViewModel.h" #include "Settings/SignerSettings.h" #include "SignersProvider.h" @@ -53,27 +53,43 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent) // workaround here - wrap widget by QDialog // TODO: fix stylesheet to support popup widgets - QDialog *d = new QDialog(this); - QVBoxLayout *l = new QVBoxLayout(d); - l->setContentsMargins(0,0,0,0); - d->setLayout(l); - d->setWindowTitle(tr("BlockSettleDB connection")); - d->resize(847, 593); + QDialog *dlg = new QDialog(this); + QVBoxLayout *layout = new QVBoxLayout(dlg); + layout->setContentsMargins(0,0,0,0); + dlg->setLayout(layout); + dlg->setWindowTitle(tr("BlockSettleDB connection")); + dlg->resize(847, 593); //FIXME: use custom dialog from resources - ArmoryServersWidget *armoryServersWidget = new ArmoryServersWidget(armoryServersProvider_, appSettings_, this); + armoryServersWidget_ = appSettings_ + ? new ArmoryServersWidget(armoryServersProvider_, appSettings_, this) + : new ArmoryServersWidget(this); // armoryServersWidget->setWindowModality(Qt::ApplicationModal); // armoryServersWidget->setWindowFlags(Qt::Dialog); - l->addWidget(armoryServersWidget); + layout->addWidget(armoryServersWidget_); - connect(armoryServersWidget, &ArmoryServersWidget::reconnectArmory, this, [this](){ + connect(dlg, &QDialog::finished, [this] { + armoryServersWidget_->deleteLater(); + armoryServersWidget_ = nullptr; + }); + connect(armoryServersWidget_, &ArmoryServersWidget::reconnectArmory, this, [this](){ emit reconnectArmory(); }); - connect(armoryServersWidget, &ArmoryServersWidget::needClose, this, [d](){ - d->reject(); + connect(armoryServersWidget_, &ArmoryServersWidget::addServer, this + , &NetworkSettingsPage::addArmoryServer); + connect(armoryServersWidget_, &ArmoryServersWidget::setServer, this + , &NetworkSettingsPage::setArmoryServer); + connect(armoryServersWidget_, &ArmoryServersWidget::delServer, this + , &NetworkSettingsPage::delArmoryServer); + connect(armoryServersWidget_, &ArmoryServersWidget::updServer, this + , &NetworkSettingsPage::updArmoryServer); + connect(armoryServersWidget_, &ArmoryServersWidget::needClose, this, [dlg](){ + dlg->reject(); }); - d->exec(); + armoryServersWidget_->onArmoryServers(armoryServers_, armorySrvCurrent_ + , armorySrvConnected_); + dlg->exec(); emit armoryServerChanged(); // Switch env if needed onArmorySelected(ui_->comboBoxArmoryServer->currentIndex()); @@ -101,14 +117,42 @@ NetworkSettingsPage::NetworkSettingsPage(QWidget* parent) }); } +void NetworkSettingsPage::init(const ApplicationSettings::State& state) +{ + if (state.find(ApplicationSettings::envConfiguration) == state.end()) { + return; // not our snapshot + } + SettingsPage::init(state); +} + +void NetworkSettingsPage::onArmoryServers(const QList& servers + , int idxCur, int idxConn) +{ + armoryServers_ = servers; + armorySrvCurrent_ = idxCur; + armorySrvConnected_ = idxConn; + if (armoryServerModel_) { + armoryServerModel_->onArmoryServers(servers, idxCur, idxConn); + } + if (armoryServersWidget_) { + armoryServersWidget_->onArmoryServers(servers, idxCur, idxConn); + } + displayArmorySettings(); +} + void NetworkSettingsPage::initSettings() { - armoryServerModel_ = new ArmoryServersViewModel(armoryServersProvider_); + if (armoryServersProvider_) { + armoryServerModel_ = new ArmoryServersViewModel(armoryServersProvider_); + connect(armoryServersProvider_.get(), &ArmoryServersProvider::dataChanged, this, &NetworkSettingsPage::displayArmorySettings); + } + else { + armoryServerModel_ = new ArmoryServersViewModel(this); + } armoryServerModel_->setSingleColumnMode(true); armoryServerModel_->setHighLightSelectedServer(false); ui_->comboBoxArmoryServer->setModel(armoryServerModel_); - connect(armoryServersProvider_.get(), &ArmoryServersProvider::dataChanged, this, &NetworkSettingsPage::displayArmorySettings); connect(ui_->comboBoxEnvironment, QOverload::of(&QComboBox::currentIndexChanged), this, &NetworkSettingsPage::onEnvSelected); connect(ui_->comboBoxArmoryServer, QOverload::of(&QComboBox::currentIndexChanged), this, &NetworkSettingsPage::onArmorySelected); } @@ -125,19 +169,32 @@ void NetworkSettingsPage::display() void NetworkSettingsPage::displayArmorySettings() { - // set index of selected server - ArmoryServer selectedServer = armoryServersProvider_->getArmorySettings(); - int selectedServerIndex = armoryServersProvider_->indexOfCurrent(); - - // Prevent NetworkSettingsPage::onArmorySelected call - auto oldBlock = ui_->comboBoxArmoryServer->blockSignals(true); + ArmoryServer selectedServer; + int selectedServerIndex = 0; + ArmoryServer connectedServerSettings; + int connectedServerIndex = 0; + if (armoryServersProvider_) { + // set index of selected server + selectedServer = armoryServersProvider_->getArmorySettings(); + selectedServerIndex = armoryServersProvider_->indexOfCurrent(); + connectedServerSettings = armoryServersProvider_->connectedArmorySettings(); + connectedServerIndex = armoryServersProvider_->indexOfConnected(); + } + else { + selectedServerIndex = armorySrvCurrent_; + if ((selectedServerIndex >= armoryServers_.size()) || (selectedServerIndex < 0)) { + return; + } + selectedServer = armoryServers_.at(selectedServerIndex); + connectedServerIndex = armorySrvConnected_; + if ((connectedServerIndex >= armoryServers_.size()) || (connectedServerIndex < 0)) { + return; + } + connectedServerSettings = armoryServers_.at(connectedServerIndex); + } ui_->comboBoxArmoryServer->setCurrentIndex(selectedServerIndex); - ui_->comboBoxArmoryServer->blockSignals(oldBlock); // display info of connected server - ArmorySettings connectedServerSettings = armoryServersProvider_->connectedArmorySettings(); - int connectedServerIndex = armoryServersProvider_->indexOfConnected(); - ui_->labelArmoryServerNetwork->setText(connectedServerSettings.netType == NetworkType::MainNet ? tr("MainNet") : tr("TestNet")); ui_->labelArmoryServerAddress->setText(connectedServerSettings.armoryDBIp); ui_->labelArmoryServerPort->setText(QString::number(connectedServerSettings.armoryDBPort)); @@ -155,7 +212,13 @@ void NetworkSettingsPage::displayArmorySettings() void NetworkSettingsPage::displayEnvironmentSettings() { - auto env = appSettings_->get(ApplicationSettings::envConfiguration); + int env = 0; + if (appSettings_) { + env = appSettings_->get(ApplicationSettings::envConfiguration); + } + else { + env = settings_.at(ApplicationSettings::envConfiguration).toInt(); + } ui_->comboBoxEnvironment->setCurrentIndex(env); onEnvSelected(env); } @@ -169,67 +232,80 @@ void NetworkSettingsPage::applyLocalSignerNetOption() void NetworkSettingsPage::reset() { - for (const auto &setting : { - ApplicationSettings::runArmoryLocally, - ApplicationSettings::netType, - ApplicationSettings::envConfiguration, - ApplicationSettings::armoryDbIp, - ApplicationSettings::armoryDbPort}) { - appSettings_->reset(setting, false); - } - display(); + const std::vector resetList{ + ApplicationSettings::runArmoryLocally, ApplicationSettings::netType + , ApplicationSettings::envConfiguration, ApplicationSettings::armoryDbIp + , ApplicationSettings::armoryDbPort + }; + if (appSettings_) { + for (const auto& setting : resetList) { + appSettings_->reset(setting, false); + } + display(); + } + else { + emit resetSettings(resetList); + } } void NetworkSettingsPage::apply() { - armoryServersProvider_->setupServer(ui_->comboBoxArmoryServer->currentIndex()); + if (armoryServersProvider_ && appSettings_ && signersProvider_) { + armoryServersProvider_->setupServer(ui_->comboBoxArmoryServer->currentIndex()); - appSettings_->set(ApplicationSettings::envConfiguration, ui_->comboBoxEnvironment->currentIndex()); + appSettings_->set(ApplicationSettings::envConfiguration, ui_->comboBoxEnvironment->currentIndex()); - if (signersProvider_->currentSignerIsLocal()) { - applyLocalSignerNetOption(); + if (signersProvider_->currentSignerIsLocal()) { + applyLocalSignerNetOption(); + } + } + else { + emit setArmoryServer(ui_->comboBoxArmoryServer->currentIndex()); + emit putSetting(ApplicationSettings::envConfiguration, ui_->comboBoxEnvironment->currentIndex()); } } void NetworkSettingsPage::onEnvSelected(int envIndex) { - auto env = ApplicationSettings::EnvConfiguration(envIndex); - if (disableSettingUpdate_) { return; } - - auto armoryServers = armoryServersProvider_->servers(); - auto armoryIndex = ui_->comboBoxArmoryServer->currentIndex(); + const auto env = ApplicationSettings::EnvConfiguration(envIndex); + const int armoryIndex = ui_->comboBoxArmoryServer->currentIndex(); + int serverIndex = armoryIndex; + const auto &armoryServers = armoryServersProvider_ ? armoryServersProvider_->servers() + : armoryServers_; if (armoryIndex < 0 || armoryIndex >= armoryServers.count()) { return; } auto armoryServer = armoryServers[armoryIndex]; - if ((armoryServer.netType == NetworkType::MainNet) != (env == ApplicationSettings::EnvConfiguration::Production)) { if (env == ApplicationSettings::EnvConfiguration::Production) { - ui_->comboBoxArmoryServer->setCurrentIndex(armoryServersProvider_->getIndexOfMainNetServer()); - } - else { - ui_->comboBoxArmoryServer->setCurrentIndex(armoryServersProvider_->getIndexOfTestNetServer()); + serverIndex = ArmoryServersProvider::getIndexOfMainNetServer(); + } else { + serverIndex = ArmoryServersProvider::getIndexOfTestNetServer(); } } + ui_->comboBoxArmoryServer->setCurrentIndex(serverIndex); } void NetworkSettingsPage::onArmorySelected(int armoryIndex) { - auto armoryServers = armoryServersProvider_->servers(); + int envIndex = ui_->comboBoxEnvironment->currentIndex(); + auto armoryServers = armoryServersProvider_ ? armoryServersProvider_->servers() + : armoryServers_; if (armoryIndex < 0 || armoryIndex >= armoryServers.count()) { return; } auto armoryServer = armoryServers[armoryIndex]; - auto envSelected = static_cast(ui_->comboBoxEnvironment->currentIndex()); + const auto envSelected = static_cast(ui_->comboBoxEnvironment->currentIndex()); if ((armoryServer.netType == NetworkType::MainNet) != (envSelected == ApplicationSettings::EnvConfiguration::Production)) { if (armoryServer.netType == NetworkType::MainNet) { - ui_->comboBoxEnvironment->setCurrentIndex(static_cast(ApplicationSettings::EnvConfiguration::Production)); + envIndex = static_cast(ApplicationSettings::EnvConfiguration::Production); } else { - ui_->comboBoxEnvironment->setCurrentIndex(static_cast(ApplicationSettings::EnvConfiguration::Test)); + envIndex = static_cast(ApplicationSettings::EnvConfiguration::Test); } } + ui_->comboBoxEnvironment->setCurrentIndex(envIndex); } diff --git a/BlockSettleUILib/Settings/NetworkSettingsPage.h b/BlockSettleUILib/Settings/NetworkSettingsPage.h index 9e82c33b4..37a57130c 100644 --- a/BlockSettleUILib/Settings/NetworkSettingsPage.h +++ b/BlockSettleUILib/Settings/NetworkSettingsPage.h @@ -20,15 +20,19 @@ namespace Ui { class ApplicationSettings; class ArmoryServersViewModel; +class ArmoryServersWidget; class NetworkSettingsPage : public SettingsPage { Q_OBJECT - public: NetworkSettingsPage(QWidget* parent = nullptr); ~NetworkSettingsPage() override; + void init(const ApplicationSettings::State&) override; + + void onArmoryServers(const QList&, int idxCur, int idxConn); + public slots: void initSettings() override; void display() override; @@ -38,6 +42,10 @@ public slots: signals: void reconnectArmory(); void armoryServerChanged(); + void setArmoryServer(int); + void addArmoryServer(const ArmoryServer&); + void delArmoryServer(int); + void updArmoryServer(int, const ArmoryServer&); private slots: void onEnvSelected(int index); @@ -50,8 +58,12 @@ private slots: private: std::unique_ptr ui_; - ArmoryServersViewModel *armoryServerModel_; + ArmoryServersViewModel* armoryServerModel_{ nullptr }; + ArmoryServersWidget* armoryServersWidget_{ nullptr }; bool disableSettingUpdate_{true}; + QList armoryServers_; + int armorySrvCurrent_{ 0 }; + int armorySrvConnected_{ 0 }; }; #endif // __NETWORK_SETTINGS_PAGE_H__ diff --git a/BlockSettleUILib/Settings/SignerSettings.cpp b/BlockSettleUILib/Settings/SignerSettings.cpp index d8fd48ce3..ca1f3bf18 100644 --- a/BlockSettleUILib/Settings/SignerSettings.cpp +++ b/BlockSettleUILib/Settings/SignerSettings.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,9 +13,9 @@ #include #include #include +#include "ArmoryConfig.h" #include "BIP150_151.h" #include "BtcDefinitions.h" -#include "BlockDataManagerConfig.h" #include "BtcUtils.h" #include "SignerSettings.h" #include "SystemFileUtils.h" @@ -193,18 +193,16 @@ bool SignerSettings::loadSettings(const std::shared_ptr &mainS if (!mainSettings) { return false; } - runMode_ = static_cast(mainSettings->runMode()); srvIDKey_ = mainSettings->serverIdKey().toHexStr(); signerPort_ = mainSettings->interfacePort(); d_->set_test_net(mainSettings->testNet()); if (d_->test_net()) { - NetworkConfig::selectNetwork(NETWORK_MODE_TESTNET); + Armory::Config::NetworkSettings::selectNetwork(Armory::Config::NETWORK_MODE_TESTNET); } else { - NetworkConfig::selectNetwork(NETWORK_MODE_MAINNET); + Armory::Config::NetworkSettings::selectNetwork(Armory::Config::NETWORK_MODE_MAINNET); } - return true; } diff --git a/BlockSettleUILib/Settings/SignerSettings.h b/BlockSettleUILib/Settings/SignerSettings.h index 7f6eebd3c..622209b71 100644 --- a/BlockSettleUILib/Settings/SignerSettings.h +++ b/BlockSettleUILib/Settings/SignerSettings.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,8 +13,8 @@ #include #include -#include "SignerDefs.h" -#include "SignerUiDefs.h" +#include "Wallets/SignerDefs.h" +#include "Wallets/SignerUiDefs.h" namespace Blocksettle { namespace Communication { namespace signer { class Settings; @@ -81,7 +81,6 @@ class SignerSettings : public QObject bool twoWaySignerAuth() const; QString dirDocuments() const; - bs::signer::ui::RunMode runMode() const { return runMode_; } bool closeHeadless() const { return true; } void setOffline(bool val); @@ -134,9 +133,7 @@ class SignerSettings : public QObject std::string fileName_; std::string srvIDKey_; int signerPort_{}; - bs::signer::ui::RunMode runMode_{}; std::unique_ptr d_; - }; diff --git a/BlockSettleUILib/Settings/SignerSettingsPage.cpp b/BlockSettleUILib/Settings/SignerSettingsPage.cpp index 622351fd9..ef1c0079f 100644 --- a/BlockSettleUILib/Settings/SignerSettingsPage.cpp +++ b/BlockSettleUILib/Settings/SignerSettingsPage.cpp @@ -18,14 +18,13 @@ #include "ApplicationSettings.h" #include "BtcUtils.h" #include "BSMessageBox.h" -#include "SignContainer.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/SignContainer.h" #include "SignersManageWidget.h" -#include "HeadlessContainer.h" SignerSettingsPage::SignerSettingsPage(QWidget* parent) : SettingsPage{parent} , ui_{new Ui::SignerSettingsPage{}} - , reset_{false} { ui_->setupUi(this); ui_->widgetTwoWayAuth->hide(); @@ -61,24 +60,36 @@ SignerSettingsPage::~SignerSettingsPage() = default; void SignerSettingsPage::display() { - ui_->checkBoxTwoWayAuth->setChecked(appSettings_->get(ApplicationSettings::twoWaySignerAuth)); - ui_->comboBoxSigner->setCurrentIndex(signersProvider_->indexOfCurrent()); + if (appSettings_ && signersProvider_) { + ui_->checkBoxTwoWayAuth->setChecked(appSettings_->get(ApplicationSettings::twoWaySignerAuth)); + ui_->comboBoxSigner->setCurrentIndex(signersProvider_->indexOfCurrent()); - showLimits(signersProvider_->indexOfCurrent() == 0); - ui_->labelTerminalKey->setText(QString::fromStdString(signersProvider_->remoteSignerOwnKey().toHexStr())); + showLimits(signersProvider_->indexOfCurrent() == 0); + ui_->labelTerminalKey->setText(QString::fromStdString(signersProvider_->remoteSignerOwnKey().toHexStr())); + } + else { + ui_->checkBoxTwoWayAuth->setChecked(settings_.at(ApplicationSettings::twoWaySignerAuth).toBool()); + ui_->comboBoxSigner->setCurrentIndex(curSignerIdx_); + showLimits(curSignerIdx_ == 0); + ui_->labelTerminalKey->setText(QString::fromStdString(ownKey_)); + } } void SignerSettingsPage::reset() { - reset_ = true; - for (const auto &setting : { ApplicationSettings::remoteSigners - , ApplicationSettings::autoSignSpendLimit - , ApplicationSettings::twoWaySignerAuth - }) { - appSettings_->reset(setting, false); + const std::vector resetList { + ApplicationSettings::remoteSigners, ApplicationSettings::autoSignSpendLimit + , ApplicationSettings::twoWaySignerAuth + }; + if (appSettings_) { + for (const auto& setting : resetList) { + appSettings_->reset(setting, false); + } + display(); + } + else { + emit resetSettings(resetList); } - display(); - reset_ = false; } void SignerSettingsPage::showHost(bool show) @@ -119,48 +130,78 @@ void SignerSettingsPage::onManageSignerKeys() // workaround here - wrap widget by QDialog // TODO: fix stylesheet to support popup widgets - QDialog *d = new QDialog(this); - QVBoxLayout *l = new QVBoxLayout(d); - l->setContentsMargins(0,0,0,0); - d->setLayout(l); - d->setWindowTitle(tr("Manage Signer Connection")); + QDialog *dlg = new QDialog(this); + QVBoxLayout *layout = new QVBoxLayout(dlg); + layout->setContentsMargins(0,0,0,0); + dlg->setLayout(layout); + dlg->setWindowTitle(tr("Manage Signer Connection")); - SignerKeysWidget *signerKeysWidget = new SignerKeysWidget(signersProvider_, appSettings_, this); - d->resize(signerKeysWidget->size()); + if (appSettings_ && signersProvider_) { + signerKeysWidget_ = new SignerKeysWidget(signersProvider_, appSettings_, this); + } + else { + signerKeysWidget_ = new SignerKeysWidget(this); + } + dlg->resize(signerKeysWidget_->size()); - l->addWidget(signerKeysWidget); + layout->addWidget(signerKeysWidget_); - connect(signerKeysWidget, &SignerKeysWidget::needClose, this, [d](){ - d->reject(); + connect(signerKeysWidget_, &SignerKeysWidget::needClose, dlg, &QDialog::reject); + connect(dlg, &QDialog::finished, [this](int) { + signerKeysWidget_->deleteLater(); + signerKeysWidget_ = nullptr; }); - - d->exec(); + signerKeysWidget_->onSignerSettings(signers_, curSignerIdx_); + dlg->exec(); emit signersChanged(); } void SignerSettingsPage::apply() { - appSettings_->set(ApplicationSettings::twoWaySignerAuth, ui_->checkBoxTwoWayAuth->isChecked()); - signersProvider_->setupSigner(ui_->comboBoxSigner->currentIndex()); + if (appSettings_) { + appSettings_->set(ApplicationSettings::twoWaySignerAuth, ui_->checkBoxTwoWayAuth->isChecked()); + signersProvider_->setupSigner(ui_->comboBoxSigner->currentIndex()); + } + else { + emit putSetting(ApplicationSettings::twoWaySignerAuth, ui_->checkBoxTwoWayAuth->isChecked()); + emit setSigner(ui_->comboBoxSigner->currentIndex()); + } } void SignerSettingsPage::initSettings() { - signersModel_ = new SignersModel(signersProvider_, this); + if (signersProvider_) { + signersModel_ = new SignersModel(signersProvider_, this); + connect(signersProvider_.get(), &SignersProvider::dataChanged, this, &SignerSettingsPage::display); + } + else { + signersModel_ = new SignersModel(this); + } signersModel_->setSingleColumnMode(true); signersModel_->setHighLightSelectedServer(false); ui_->comboBoxSigner->setModel(signersModel_); +} - connect(signersProvider_.get(), &SignersProvider::dataChanged, this, &SignerSettingsPage::display); +void SignerSettingsPage::init(const ApplicationSettings::State& state) +{ + if (state.find(ApplicationSettings::twoWaySignerAuth) == state.end()) { + return; // not our snapshot + } + SettingsPage::init(state); } -void SignerSettingsPage::init(const std::shared_ptr &appSettings - , const std::shared_ptr &armoryServersProvider - , const std::shared_ptr &signersProvider, const std::shared_ptr &signContainer - , const std::shared_ptr &walletsMgr) +void SignerSettingsPage::onSignerSettings(const QList& signers + , const std::string& ownKey, int idxCur) { - reset_ = true; - SettingsPage::init(appSettings, armoryServersProvider, signersProvider, signContainer, walletsMgr); - reset_ = false; + signers_ = signers; + curSignerIdx_ = idxCur; + ownKey_ = ownKey; + if (signersModel_) { + signersModel_->onSignerSettings(signers, idxCur); + } + if (signerKeysWidget_) { + signerKeysWidget_->onSignerSettings(signers, idxCur); + } + display(); } diff --git a/BlockSettleUILib/Settings/SignerSettingsPage.h b/BlockSettleUILib/Settings/SignerSettingsPage.h index 21a64f324..e8409d834 100644 --- a/BlockSettleUILib/Settings/SignerSettingsPage.h +++ b/BlockSettleUILib/Settings/SignerSettingsPage.h @@ -15,12 +15,11 @@ #include "ConfigDialog.h" #include "SignersModel.h" - namespace Ui { class SignerSettingsPage; -}; - +} class ApplicationSettings; +class SignerKeysWidget; class SignerSettingsPage : public SettingsPage @@ -34,11 +33,9 @@ class SignerSettingsPage : public SettingsPage void reset() override; void apply() override; void initSettings() override; - void init(const std::shared_ptr &appSettings - , const std::shared_ptr &armoryServersProvider - , const std::shared_ptr &signersProvider - , const std::shared_ptr &signContainer - , const std::shared_ptr &walletsMgr) override; + void init(const ApplicationSettings::State&) override; + + void onSignerSettings(const QList&, const std::string& ownKey, int idxCur); private slots: void onAsSpendLimitChanged(double); @@ -46,6 +43,7 @@ private slots: signals: void signersChanged(); + void setSigner(int); private: void showHost(bool); @@ -55,8 +53,11 @@ private slots: private: std::unique_ptr ui_; - SignersModel *signersModel_; - bool reset_{}; + SignersModel* signersModel_{ nullptr }; + SignerKeysWidget* signerKeysWidget_{ nullptr }; + QList signers_; + int curSignerIdx_{ 0 }; + std::string ownKey_; }; #endif // __SIGNER_SETTINGS_PAGE_H__ diff --git a/BlockSettleUILib/Settings/SignersManageWidget.cpp b/BlockSettleUILib/Settings/SignersManageWidget.cpp index 7807f550e..5b34bd4f3 100644 --- a/BlockSettleUILib/Settings/SignersManageWidget.cpp +++ b/BlockSettleUILib/Settings/SignersManageWidget.cpp @@ -54,27 +54,8 @@ SignerKeysWidget::SignerKeysWidget(const std::shared_ptr &signe emit needClose(); }); - connect(ui_->tableViewSignerKeys->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this](const QItemSelection &selected, const QItemSelection &deselected){ - // this check will prevent loop selectionChanged -> setupSignerFromSelected -> select -> selectionChanged - if (deselected.isEmpty()) { - return; - } - - ui_->pushButtonSelect->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); - ui_->pushButtonDeleteSignerKey->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); - ui_->pushButtonEditSignerKey->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); - - if (selected.indexes().first().row() == 0) { - ui_->pushButtonDeleteSignerKey->setDisabled(true); - ui_->pushButtonEditSignerKey->setDisabled(true); - } - - resetForm(); - - // save to settings right after row highlight - setupSignerFromSelected(true); - }); + connect(ui_->tableViewSignerKeys->selectionModel(), &QItemSelectionModel::selectionChanged + , this, &SignerKeysWidget::onSelectionChanged); resetForm(); @@ -96,27 +77,111 @@ SignerKeysWidget::SignerKeysWidget(const std::shared_ptr &signe ui_->pushButtonSelect->hide(); } +SignerKeysWidget::SignerKeysWidget(QWidget* parent) + : QWidget(parent) + , ui_(new Ui::SignerKeysWidget) +{ + signersModel_ = new SignersModel(this); + ui_->setupUi(this); + + ui_->spinBoxPort->setMinimum(0); + ui_->spinBoxPort->setMaximum(USHRT_MAX); + + ui_->tableViewSignerKeys->setModel(signersModel_); + + int defaultSectionSize = ui_->tableViewSignerKeys->horizontalHeader()->defaultSectionSize(); + ui_->tableViewSignerKeys->horizontalHeader()->resizeSection(0, defaultSectionSize * 2); + ui_->tableViewSignerKeys->horizontalHeader()->resizeSection(1, defaultSectionSize); + ui_->tableViewSignerKeys->horizontalHeader()->resizeSection(2, defaultSectionSize); + ui_->tableViewSignerKeys->horizontalHeader()->setStretchLastSection(true); + + connect(ui_->pushButtonAddSignerKey, &QPushButton::clicked, this, &SignerKeysWidget::onAddSignerKey); + connect(ui_->pushButtonDeleteSignerKey, &QPushButton::clicked, this, &SignerKeysWidget::onDeleteSignerKey); + connect(ui_->pushButtonEditSignerKey, &QPushButton::clicked, this, &SignerKeysWidget::onEdit); + connect(ui_->pushButtonCancelSaveSignerKey, &QPushButton::clicked, this, &SignerKeysWidget::resetForm); + connect(ui_->pushButtonSaveSignerKey, &QPushButton::clicked, this, &SignerKeysWidget::onSave); + connect(ui_->pushButtonSelect, &QPushButton::clicked, this, &SignerKeysWidget::onSelect); + connect(ui_->pushButtonKeyImport, &QPushButton::clicked, this, &SignerKeysWidget::onKeyImport); + + connect(ui_->lineEditName, &QLineEdit::textChanged, this, &SignerKeysWidget::onFormChanged); + connect(ui_->lineEditAddress, &QLineEdit::textChanged, this, &SignerKeysWidget::onFormChanged); + connect(ui_->spinBoxPort, QOverload::of(&QSpinBox::valueChanged), this, &SignerKeysWidget::onFormChanged); + + connect(ui_->pushButtonClose, &QPushButton::clicked, this, [this]() { + emit needClose(); + }); + + connect(ui_->tableViewSignerKeys->selectionModel(), &QItemSelectionModel::selectionChanged + , this, &SignerKeysWidget::onSelectionChanged); + + resetForm(); + + ui_->pushButtonDeleteSignerKey->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); + ui_->pushButtonEditSignerKey->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); + + auto validator = new QRegExpValidator(this); + validator->setRegExp(kRxAddress); + ui_->lineEditAddress->setValidator(validator); + onFormChanged(); + + // TODO: remove select signer button if it's not required anymore + ui_->pushButtonSelect->hide(); +} + +void SignerKeysWidget::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + // this check will prevent loop selectionChanged -> setupSignerFromSelected -> select -> selectionChanged + if (deselected.isEmpty()) { + return; + } + + ui_->pushButtonSelect->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); + ui_->pushButtonDeleteSignerKey->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); + ui_->pushButtonEditSignerKey->setDisabled(ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()); + + if (selected.indexes().first().row() == 0) { + ui_->pushButtonDeleteSignerKey->setDisabled(true); + ui_->pushButtonEditSignerKey->setDisabled(true); + } + + resetForm(); + + // save to settings right after row highlight + setupSignerFromSelected(true); +} + void SignerKeysWidget::setRowSelected(int row) { QModelIndex currentIndex; - if (signersProvider_->signers().size() >= 0) { + if (signersProvider_ && (signersProvider_->signers().size() >= 0)) { int indexOfCurrent = row; if (indexOfCurrent < 0 || indexOfCurrent >= signersProvider_->signers().size()) { indexOfCurrent = 0; } currentIndex = signersModel_->index(indexOfCurrent, 0); } + else { + currentIndex = signersModel_->index(row, 0); + } ui_->tableViewSignerKeys->selectionModel()->select(currentIndex - , QItemSelectionModel::Select | QItemSelectionModel::Rows); + , QItemSelectionModel::Select | QItemSelectionModel::Rows); +} + +void SignerKeysWidget::onSignerSettings(const QList& signers + , int idxCur) +{ + signers_ = signers; + signersModel_->onSignerSettings(signers, idxCur); + setRowSelected(idxCur); } SignerKeysWidget::~SignerKeysWidget() = default; void SignerKeysWidget::onAddSignerKey() { - if (ui_->lineEditName->text().isEmpty() || ui_->lineEditAddress->text().isEmpty()) + if (ui_->lineEditName->text().isEmpty() || ui_->lineEditAddress->text().isEmpty()) { return; - + } SignerHost signerHost; signerHost.name = ui_->lineEditName->text(); @@ -124,11 +189,17 @@ void SignerKeysWidget::onAddSignerKey() signerHost.port = ui_->spinBoxPort->value(); signerHost.key = ui_->lineEditKey->text(); - signersProvider_->add(signerHost); - resetForm(); + if (signersProvider_) { + signersProvider_->add(signerHost); + resetForm(); - setRowSelected(signersProvider_->signers().size() - 1); - setupSignerFromSelected(true); + setRowSelected(signersProvider_->signers().size() - 1); + setupSignerFromSelected(true); + } + else { + resetForm(); + emit addSigner(signerHost); + } } void SignerKeysWidget::onDeleteSignerKey() @@ -142,11 +213,17 @@ void SignerKeysWidget::onDeleteSignerKey() return; } - signersProvider_->remove(selectedRow); - resetForm(); + if (signersProvider_) { + signersProvider_->remove(selectedRow); + resetForm(); - setRowSelected(0); - setupSignerFromSelected(true); + setRowSelected(0); + setupSignerFromSelected(true); + } + else { + resetForm(); + emit delSigner(selectedRow); + } } void SignerKeysWidget::onEdit() @@ -156,11 +233,12 @@ void SignerKeysWidget::onEdit() } int index = ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().first().row(); - if (index >= signersProvider_->signers().size()) { + if (signersProvider_ && (index >= signersProvider_->signers().size())) { return; } - SignerHost signerHost = signersProvider_->signers().at(index); + SignerHost signerHost = signersProvider_ ? signersProvider_->signers().at(index) + : signers_.at(index); ui_->stackedWidgetAddSave->setCurrentWidget(ui_->pageSaveSignerKeyButton); ui_->lineEditName->setText(signerHost.name); @@ -182,7 +260,12 @@ void SignerKeysWidget::onSave() signerHost.port = ui_->spinBoxPort->value(); signerHost.key = ui_->lineEditKey->text(); - signersProvider_->replace(index, signerHost); + if (signersProvider_) { + signersProvider_->replace(index, signerHost); + } + else { + emit updSigner(index, signerHost); + } resetForm(); } @@ -209,8 +292,20 @@ void SignerKeysWidget::onFormChanged() signerHost.key = ui_->lineEditKey->text(); valid = signerHost.isValid(); if (valid) { - exists = signersProvider_->indexOf(signerHost.name) != -1 + if (signersProvider_) { + exists = signersProvider_->indexOf(signerHost.name) != -1 || signersProvider_->indexOf(signerHost) != -1; + } + else { + for (const auto& signer : signers_) { + if ((signer.name == signerHost.name) || + ((signer.address == signerHost.address) && + (signer.port == signerHost.port))) { + exists = true; + break; + } + } + } } } ui_->pushButtonAddSignerKey->setEnabled(valid && acceptable && !exists); @@ -239,12 +334,12 @@ void SignerKeysWidget::setupSignerFromSelected(bool needUpdate) if (ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().isEmpty()) { return; } - int index = ui_->tableViewSignerKeys->selectionModel()->selectedIndexes().first().row(); - if (index >= signersProvider_->signers().size()) { - return; + if (signersProvider_) { + if (index >= signersProvider_->signers().size()) { + return; + } + signersProvider_->setupSigner(index, needUpdate); + setRowSelected(signersProvider_->indexOfCurrent()); } - - signersProvider_->setupSigner(index, needUpdate); - setRowSelected(signersProvider_->indexOfCurrent()); } diff --git a/BlockSettleUILib/Settings/SignersManageWidget.h b/BlockSettleUILib/Settings/SignersManageWidget.h index fd782b498..a4c8ba85b 100644 --- a/BlockSettleUILib/Settings/SignersManageWidget.h +++ b/BlockSettleUILib/Settings/SignersManageWidget.h @@ -11,6 +11,7 @@ #ifndef SIGNERS_MANAGE_WIDGET_H #define SIGNERS_MANAGE_WIDGET_H +#include #include #include @@ -25,12 +26,15 @@ class SignerKeysWidget : public QWidget Q_OBJECT public: - explicit SignerKeysWidget(const std::shared_ptr &signersProvider + [[deprecated]] explicit SignerKeysWidget(const std::shared_ptr &signersProvider , const std::shared_ptr &appSettings, QWidget *parent = nullptr); + explicit SignerKeysWidget(QWidget* parent = nullptr); ~SignerKeysWidget(); void setRowSelected(int row); + void onSignerSettings(const QList&, int idxCur); + public slots: void onAddSignerKey(); void onDeleteSignerKey(); @@ -41,6 +45,12 @@ public slots: signals: void needClose(); + void addSigner(const SignerHost&); + void delSigner(int); + void updSigner(int, const SignerHost&); + +private slots: + void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); private: void setupSignerFromSelected(bool needUpdate); @@ -53,8 +63,9 @@ private slots: std::unique_ptr ui_; std::shared_ptr appSettings_; std::shared_ptr signersProvider_; + QList signers_; - SignersModel *signersModel_; + SignersModel* signersModel_{ nullptr }; }; #endif // SIGNERS_MANAGE_WIDGET_H diff --git a/BlockSettleUILib/Settings/SignersModel.cpp b/BlockSettleUILib/Settings/SignersModel.cpp index d02549f1b..f6e897637 100644 --- a/BlockSettleUILib/Settings/SignersModel.cpp +++ b/BlockSettleUILib/Settings/SignersModel.cpp @@ -21,6 +21,10 @@ SignersModel::SignersModel(const std::shared_ptr& signersProvid connect(signersProvider.get(), &SignersProvider::dataChanged, this, &SignersModel::update); } +SignersModel::SignersModel(QObject* parent) + : QAbstractTableModel(parent) +{} + int SignersModel::columnCount(const QModelIndex&) const { return static_cast(SignersModel::ColumnsCount); @@ -39,7 +43,8 @@ QVariant SignersModel::data(const QModelIndex &index, int role) const SignerHost signerHost = signers_.at(index.row()); - int currentServerIndex = signersProvider_->indexOfCurrent(); + const int currentServerIndex = signersProvider_ ? signersProvider_->indexOfCurrent() + : currentServerIndex_; if (role == Qt::FontRole && index.row() == currentServerIndex) { // QFont font; @@ -100,6 +105,15 @@ void SignersModel::setSingleColumnMode(bool singleColumnMode) singleColumnMode_ = singleColumnMode; } +void SignersModel::onSignerSettings(const QList& signers + , int idxCur) +{ + currentServerIndex_ = idxCur; + beginResetModel(); + signers_ = signers; + endResetModel(); +} + void SignersModel::setHighLightSelectedServer(bool highLightSelectedServer) { highLightSelectedServer_ = highLightSelectedServer; diff --git a/BlockSettleUILib/Settings/SignersModel.h b/BlockSettleUILib/Settings/SignersModel.h index f2dd4fb45..63e954389 100644 --- a/BlockSettleUILib/Settings/SignersModel.h +++ b/BlockSettleUILib/Settings/SignersModel.h @@ -21,8 +21,9 @@ class SignersModel : public QAbstractTableModel { public: - SignersModel(const std::shared_ptr &signersProvider - , QObject *parent = nullptr); + [[deprecated]] SignersModel(const std::shared_ptr &signersProvider + , QObject *parent = nullptr); + SignersModel(QObject* parent = nullptr); ~SignersModel() noexcept = default; SignersModel(const SignersModel&) = delete; @@ -40,12 +41,15 @@ class SignersModel : public QAbstractTableModel void setHighLightSelectedServer(bool highLightSelectedServer); void setSingleColumnMode(bool singleColumnMode); + void onSignerSettings(const QList &, int idxCur); + public slots: void update(); private: std::shared_ptr signersProvider_; QList signers_; + int currentServerIndex_{ 0 }; bool highLightSelectedServer_ = true; bool singleColumnMode_ = false; diff --git a/BlockSettleUILib/Settings/SignersProvider.cpp b/BlockSettleUILib/Settings/SignersProvider.cpp index bf4f5e3df..5b568c717 100644 --- a/BlockSettleUILib/Settings/SignersProvider.cpp +++ b/BlockSettleUILib/Settings/SignersProvider.cpp @@ -12,7 +12,7 @@ #include #include -#include "SignContainer.h" +#include "Wallets/SignContainer.h" #include "SystemFileUtils.h" #include "TransportBIP15x.h" @@ -259,8 +259,9 @@ void SignersProvider::setupSigner(int index, bool needUpdate) if (index >= 0 && index < signerList.size()) { appSettings_->set(ApplicationSettings::signerIndex, index); - if (needUpdate) + if (needUpdate) { emit dataChanged(); + } } } diff --git a/BlockSettleUILib/StatusBarView.cpp b/BlockSettleUILib/StatusBarView.cpp index 224bf2753..a410d4d44 100644 --- a/BlockSettleUILib/StatusBarView.cpp +++ b/BlockSettleUILib/StatusBarView.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -10,25 +10,18 @@ */ #include "StatusBarView.h" #include "AssetManager.h" +#include "Wallets/HeadlessContainer.h" #include "UiUtils.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" #include -StatusBarView::StatusBarView(const std::shared_ptr &armory - , const std::shared_ptr &walletsManager - , std::shared_ptr assetManager, const std::shared_ptr &celerClient - , const std::shared_ptr &container, QStatusBar *parent) +StatusBarView::StatusBarView(QStatusBar *parent) : QObject(nullptr) , statusBar_(parent) , iconSize_(16, 16) - , armoryConnState_(ArmoryState::Offline) - , walletsManager_(walletsManager) - , assetManager_(assetManager) { - init(armory.get()); - for (int s : {16, 24, 32}) { iconCeler_.addFile(QString(QLatin1String(":/ICON_BS_%1")).arg(s), QSize(s, s), QIcon::Normal); @@ -82,32 +75,10 @@ StatusBarView::StatusBarView(const std::shared_ptr &armory SetLoggedOutStatus(); - connect(assetManager_.get(), &AssetManager::totalChanged, this, &StatusBarView::updateBalances); - connect(assetManager_.get(), &AssetManager::securitiesChanged, this, &StatusBarView::updateBalances); - - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletImportStarted, this, &StatusBarView::onWalletImportStarted); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletImportFinished, this, &StatusBarView::onWalletImportFinished); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, &StatusBarView::updateBalances); - - connect(celerClient.get(), &BaseCelerClient::OnConnectedToServer, this, &StatusBarView::onConnectedToServer); - connect(celerClient.get(), &BaseCelerClient::OnConnectionClosed, this, &StatusBarView::onConnectionClosed); - connect(celerClient.get(), &BaseCelerClient::OnConnectionError, this, &StatusBarView::onConnectionError); - - // container might be null if user rejects remote signer key - if (container) { - // connected are not used here because we wait for authenticated signal instead - // disconnected are not used here because onContainerError should be always called - connect(container.get(), &SignContainer::authenticated, this, &StatusBarView::onContainerAuthorized); - connect(container.get(), &SignContainer::connectionError, this, &StatusBarView::onContainerError); - } - - onArmoryStateChanged(armory_->state()); - onConnectionClosed(); - setBalances(); + onDisconnectedFromMatching(); + connectionStatusLabel_->setPixmap(iconContainerOffline_); containerStatusLabel_->setPixmap(iconContainerOffline_); - - connectionStatusLabel_->installEventFilter(this); } StatusBarView::~StatusBarView() noexcept @@ -123,12 +94,67 @@ StatusBarView::~StatusBarView() noexcept separator->deleteLater(); } - cleanup(); + if (armory_) { + cleanup(); + } +} + +void StatusBarView::onBlockchainStateChanged(int state, unsigned int blockNum) +{ + onArmoryStateChanged(static_cast(state), blockNum); +} + +void StatusBarView::onXbtBalance(const bs::sync::WalletBalanceData &wbd) +{ // uppercase eliminates ext-int balance duplication + xbtBalances_[QString::fromStdString(wbd.id).toUpper().toStdString()] = wbd.balTotal; + if (balanceSymbols_.empty() || (balanceSymbols_[0] != bs::network::XbtCurrency)) { + balanceSymbols_.insert(balanceSymbols_.cbegin(), bs::network::XbtCurrency); + } + + BTCNumericTypes::balance_type accBalance = 0; + for (const auto& bal : xbtBalances_) { + accBalance += bal.second; + } + balances_[bs::network::XbtCurrency] = accBalance; + displayBalances(); +} + +void StatusBarView::displayBalances() +{ + QString text; + for (const auto& currency : balanceSymbols_) { + if (currency == bs::network::XbtCurrency) { + QString xbt; + switch (armoryConnState_) { + case ArmoryState::Ready: + xbt = UiUtils::displayAmount(balances_.at(currency)); + break; + case ArmoryState::Scanning: + case ArmoryState::Connected: + xbt = tr("Loading..."); + break; + case ArmoryState::Closing: + case ArmoryState::Offline: + default: + xbt = tr("..."); + break; + } + text += tr(" XBT: %1 ").arg(xbt); + } else { + text += tr("| %1: %2 ") + .arg(QString::fromStdString(currency)) + .arg(UiUtils::displayCurrencyAmount(balances_.at(currency))); + } + } + balanceLabel_->setText(text); + progressBar_->setVisible(false); + estimateLabel_->setVisible(false); + connectionStatusLabel_->show(); } void StatusBarView::onStateChanged(ArmoryState state) { - QMetaObject::invokeMethod(this, [this, state] { onArmoryStateChanged(state); }); + QMetaObject::invokeMethod(this, [this, state] { onArmoryStateChanged(state, blockNum_); }); } void StatusBarView::onError(int, const std::string &errMsg) @@ -150,10 +176,11 @@ void StatusBarView::onPrepareConnection(NetworkType netType, const std::string & QMetaObject::invokeMethod(this, [this, netType] { onPrepareArmoryConnection(netType); }); } -void StatusBarView::onNewBlock(unsigned, unsigned) +void StatusBarView::onNewBlock(unsigned topBlock, unsigned) { - QMetaObject::invokeMethod(this, [this] { + QMetaObject::invokeMethod(this, [this, topBlock] { timeSinceLastBlock_ = std::chrono::steady_clock::now(); + updateConnectionStatusDetails(static_cast(armoryState_), topBlock); }); } @@ -192,19 +219,23 @@ void StatusBarView::onPrepareArmoryConnection(NetworkType netType) progressBar_->setVisible(false); estimateLabel_->setVisible(false); - onArmoryStateChanged(ArmoryState::Offline); + onArmoryStateChanged(ArmoryState::Offline, 0); } -void StatusBarView::onArmoryStateChanged(ArmoryState state) +void StatusBarView::onArmoryStateChanged(ArmoryState state, unsigned int topBlock) { - progressBar_->setVisible(false); - estimateLabel_->setVisible(false); + armoryState_ = (int)state; + blockNum_ = topBlock; + + progressBar_->hide(); + estimateLabel_->hide(); connectionStatusLabel_->show(); armoryConnState_ = state; setBalances(); + // for some reason previous icons don't display at all now switch (state) { case ArmoryState::Scanning: case ArmoryState::Connecting: @@ -214,16 +245,18 @@ void StatusBarView::onArmoryStateChanged(ArmoryState state) case ArmoryState::Closing: case ArmoryState::Offline: case ArmoryState::Cancelled: - connectionStatusLabel_->setPixmap(iconOffline_); + connectionStatusLabel_->setPixmap(/*iconOffline_*/iconContainerOnline_); break; case ArmoryState::Ready: - connectionStatusLabel_->setPixmap(iconOnline_); + connectionStatusLabel_->setPixmap(/*iconOnline_*/iconContainerOnline_); updateBalances(); break; default: break; } + + updateConnectionStatusDetails(state, topBlock); } void StatusBarView::onArmoryProgress(BDMPhase phase, float progress, unsigned int secondsRem) @@ -260,6 +293,9 @@ void StatusBarView::onArmoryError(QString errorMessage) void StatusBarView::setBalances() { + if (!walletsManager_) { + return; + } QString xbt; switch (armoryConnState_) { @@ -284,17 +320,34 @@ void StatusBarView::setBalances() QString text = tr(" XBT: %1 ").arg(xbt); for (const auto& currency : assetManager_->currencies()) { - text += tr("| %1: %2 ") - .arg(QString::fromStdString(currency)) - .arg(UiUtils::displayCurrencyAmount(assetManager_->getBalance(currency, false, nullptr))); + if (currency != "EURP" && currency != "EURD") { + text += tr("| %1: %2 ") + .arg(QString::fromStdString(currency)) + .arg(UiUtils::displayCurrencyAmount(assetManager_->getBalance(currency, false, nullptr))); + } } balanceLabel_->setText(text); } -void StatusBarView::updateConnectionStatusDetails() +void StatusBarView::onBalanceUpdated(const std::string &symbol, double balance) +{ + balances_[symbol] = balance; + const auto &it = std::find(balanceSymbols_.cbegin(), balanceSymbols_.cend(), symbol); + if (it == balanceSymbols_.cend()) { + if (symbol == bs::network::XbtCurrency) { + balanceSymbols_.insert(balanceSymbols_.cbegin(), symbol); + } + else { + balanceSymbols_.push_back(symbol); + } + } + displayBalances(); +} + +void StatusBarView::updateConnectionStatusDetails(ArmoryState state, unsigned int topBlock) { - switch (armory_->state()) { + switch (state) { case ArmoryState::Scanning: case ArmoryState::Connecting: case ArmoryState::Connected: @@ -309,7 +362,7 @@ void StatusBarView::updateConnectionStatusDetails() case ArmoryState::Ready: { auto lastBlockMinutes = (std::chrono::steady_clock::now() - timeSinceLastBlock_) / std::chrono::minutes(1); - auto tooltip = tr("Connected to DB (%1 blocks, last block updated %2 minute(s) ago)").arg(armory_->topBlock()).arg(lastBlockMinutes); + auto tooltip = tr("Connected to DB (%1 blocks, last block updated %2 minute(s) ago)").arg(blockNum_).arg(lastBlockMinutes); connectionStatusLabel_->setToolTip(tooltip); break; } @@ -386,40 +439,22 @@ void StatusBarView::SetCelerConnectingStatus() celerConnectionIconLabel_->setPixmap(iconCelerConnecting_); } -void StatusBarView::onConnectedToServer() +void StatusBarView::onConnectedToMatching() { SetLoggedinStatus(); } -void StatusBarView::onConnectionClosed() +void StatusBarView::onDisconnectedFromMatching() { SetLoggedOutStatus(); } -void StatusBarView::onConnectionError(int errorCode) -{ - switch(errorCode) - { - case BaseCelerClient::ResolveHostError: - statusBar_->showMessage(tr("Could not resolve Celer host")); - break; - case BaseCelerClient::LoginError: - statusBar_->showMessage(tr("Invalid login/password pair"), 2000); - break; - case BaseCelerClient::ServerMaintainanceError: - statusBar_->showMessage(tr("Server maintainance")); - break; - case BaseCelerClient::UndefinedError: - break; - } -} - void StatusBarView::onContainerAuthorized() { containerStatusLabel_->setPixmap(iconContainerOnline_); } -void StatusBarView::onContainerError(SignContainer::ConnectionError error, const QString &details) +void StatusBarView::onSignerStatusChanged(SignContainer::ConnectionError error, const QString &details) { Q_UNUSED(details); @@ -428,6 +463,10 @@ void StatusBarView::onContainerError(SignContainer::ConnectionError error, const assert(false); break; + case SignContainer::Ready: + containerStatusLabel_->setPixmap(iconContainerOnline_); + break; + case SignContainer::UnknownError: case SignContainer::SocketFailed: case SignContainer::HostNotFound: @@ -497,7 +536,7 @@ QString StatusBarView::getImportingText() const bool StatusBarView::eventFilter(QObject *object, QEvent *event) { if (object == connectionStatusLabel_ && event->type() == QEvent::ToolTip) { - updateConnectionStatusDetails(); + updateConnectionStatusDetails(static_cast(armoryState_), blockNum_); } return false; } diff --git a/BlockSettleUILib/StatusBarView.h b/BlockSettleUILib/StatusBarView.h index f3676fc9f..503a4f755 100644 --- a/BlockSettleUILib/StatusBarView.h +++ b/BlockSettleUILib/StatusBarView.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -19,9 +19,8 @@ #include #include "ArmoryConnection.h" -#include "CelerClient.h" #include "CircleProgressBar.h" -#include "SignContainer.h" +#include "Wallets/SignContainer.h" namespace bs { namespace sync { @@ -29,16 +28,13 @@ namespace bs { } } class AssetManager; -class SignContainer; +class HeadlessContainer; class StatusBarView : public QObject, public ArmoryCallbackTarget { Q_OBJECT public: - StatusBarView(const std::shared_ptr & - , const std::shared_ptr & - , std::shared_ptr assetManager, const std::shared_ptr & - , const std::shared_ptr &, QStatusBar *parent); + StatusBarView(QStatusBar *parent); ~StatusBarView() noexcept override; StatusBarView(const StatusBarView&) = delete; @@ -46,27 +42,29 @@ class StatusBarView : public QObject, public ArmoryCallbackTarget StatusBarView(StatusBarView&&) = delete; StatusBarView& operator = (StatusBarView&&) = delete; -private slots: +public slots: + void onBalanceUpdated(const std::string &symbol, double balance); void onPrepareArmoryConnection(NetworkType); - void onArmoryStateChanged(ArmoryState); + void onArmoryStateChanged(ArmoryState, unsigned int topBlock); void onArmoryProgress(BDMPhase, float progress, unsigned int secondsRem); void onArmoryError(QString); - void onConnectedToServer(); - void onConnectionClosed(); - void onConnectionError(int errorCode); + void onConnectedToMatching(); + void onDisconnectedFromMatching(); void onContainerAuthorized(); - void onContainerError(SignContainer::ConnectionError error, const QString &details); - void updateBalances(); + void onSignerStatusChanged(SignContainer::ConnectionError error, const QString &details); + void updateBalances(); //deprecated void onWalletImportStarted(const std::string &walletId); void onWalletImportFinished(const std::string &walletId); + void onBlockchainStateChanged(int, unsigned int); + void onXbtBalance(const bs::sync::WalletBalanceData&); private: - void onStateChanged(ArmoryState) override; - void onError(int errCode, const std::string &) override; - void onLoadProgress(BDMPhase, float, unsigned int, unsigned int) override; - void onPrepareConnection(NetworkType, const std::string &host + [[deprecated]] void onStateChanged(ArmoryState) override; + [[deprecated]] void onError(int errCode, const std::string &) override; + [[deprecated]] void onLoadProgress(BDMPhase, float, unsigned int, unsigned int) override; + [[deprecated]] void onPrepareConnection(NetworkType, const std::string &host , const std::string &port) override; - void onNewBlock(unsigned, unsigned) override; + [[deprecated]] void onNewBlock(unsigned, unsigned) override; public: void updateDBHeadersProgress(float progress, unsigned secondsRem); @@ -78,16 +76,16 @@ private slots: private: void setupBtcIcon(NetworkType); void SetLoggedinStatus(); - void SetCelerErrorStatus(const QString& message); void SetLoggedOutStatus(); void SetCelerConnectingStatus(); QWidget *CreateSeparator(); - void setBalances(); - void updateConnectionStatusDetails(); + [[deprecated]] void setBalances(); + void displayBalances(); + void updateConnectionStatusDetails(ArmoryState state, unsigned int blockNum); private: void updateProgress(float progress, unsigned secondsRem); - QString getImportingText() const; + [[deprecated]] QString getImportingText() const; bool eventFilter(QObject *object, QEvent *event) override; @@ -120,6 +118,11 @@ private slots: std::shared_ptr walletsManager_; std::shared_ptr assetManager_; std::unordered_set importingWallets_; + std::vector balanceSymbols_; + std::unordered_map balances_; + std::unordered_map xbtBalances_; + int armoryState_{ -1 }; + unsigned int blockNum_{ 0 }; std::chrono::steady_clock::time_point timeSinceLastBlock_{std::chrono::steady_clock::now()}; }; diff --git a/BlockSettleUILib/Stylesheet/stylesheet.css b/BlockSettleUILib/Stylesheet/stylesheet.css index b8a845a78..1511eca59 100644 --- a/BlockSettleUILib/Stylesheet/stylesheet.css +++ b/BlockSettleUILib/Stylesheet/stylesheet.css @@ -1696,3 +1696,13 @@ QDialog#LoginWindow QWidget#widgetSignup[testEnv="true"] { color: #5097bd; text-decoration: underline; text-transform: none;} + +#labelFuturePrice[selectedLine="true"] { + color: #ffffff; +} +#labelFutureBalanceValue[positiveColor="true"] { + color: #22C064; +} +#labelFutureBalanceValue[negativeColor="true"] { + color: #EC0A35; +} diff --git a/BlockSettleUILib/Trading/AutoSignQuoteProvider.cpp b/BlockSettleUILib/Trading/AutoSignQuoteProvider.cpp deleted file mode 100644 index 5771faa80..000000000 --- a/BlockSettleUILib/Trading/AutoSignQuoteProvider.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "AutoSignQuoteProvider.h" - -#include "SignContainer.h" -#include "WalletManager.h" -#include "Wallets/SyncWalletsManager.h" -#include "Wallets/SyncHDWallet.h" -#include "UserScriptRunner.h" - -#include -#include -#include -#include - - -AutoSignScriptProvider::AutoSignScriptProvider(const std::shared_ptr &logger - , UserScriptRunner *scriptRunner - , const std::shared_ptr &appSettings - , const std::shared_ptr &container - , const std::shared_ptr &celerClient - , QObject *parent) - : QObject(parent), logger_(logger), scriptRunner_(scriptRunner) - , appSettings_(appSettings) - , signingContainer_(container) - , celerClient_(celerClient) -{ - scriptRunner_->setParent(this); - - if (walletsManager_) { - scriptRunner_->setWalletsManager(walletsManager_); - } - - if (signingContainer_) { - connect(signingContainer_.get(), &SignContainer::ready, this - , &AutoSignScriptProvider::onSignerStateUpdated, Qt::QueuedConnection); - connect(signingContainer_.get(), &SignContainer::disconnected, this - , &AutoSignScriptProvider::onSignerStateUpdated, Qt::QueuedConnection); - connect(signingContainer_.get(), &SignContainer::AutoSignStateChanged, this - , &AutoSignScriptProvider::onAutoSignStateChanged); - } - - connect(scriptRunner_, &UserScriptRunner::scriptLoaded, this, &AutoSignScriptProvider::onScriptLoaded); - connect(scriptRunner_, &UserScriptRunner::failedToLoad, this, &AutoSignScriptProvider::onScriptFailed); - - onSignerStateUpdated(); - - connect(celerClient_.get(), &BaseCelerClient::OnConnectedToServer, this, &AutoSignScriptProvider::onConnectedToCeler); - connect(celerClient_.get(), &BaseCelerClient::OnConnectionClosed, this, &AutoSignScriptProvider::onDisconnectedFromCeler); -} - -void AutoSignScriptProvider::onSignerStateUpdated() -{ - disableAutoSign(); - scriptRunner_->disable(); - - emit autoSignQuoteAvailabilityChanged(); -} - -void AutoSignScriptProvider::disableAutoSign() -{ - if (!walletsManager_) { - return; - } - - const auto wallet = walletsManager_->getPrimaryWallet(); - if (!wallet) { - logger_->error("Failed to obtain auto-sign primary wallet"); - return; - } - - QVariantMap data; - data[QLatin1String("rootId")] = QString::fromStdString(wallet->walletId()); - data[QLatin1String("enable")] = false; - signingContainer_->customDialogRequest(bs::signer::ui::GeneralDialogType::ActivateAutoSign, data); -} - -void AutoSignScriptProvider::tryEnableAutoSign() -{ - if (!walletsManager_ || !signingContainer_) { - return; - } - - const auto wallet = walletsManager_->getPrimaryWallet(); - if (!wallet) { - logger_->error("Failed to obtain auto-sign primary wallet"); - return; - } - - QVariantMap data; - data[QLatin1String("rootId")] = QString::fromStdString(wallet->walletId()); - data[QLatin1String("enable")] = true; - signingContainer_->customDialogRequest(bs::signer::ui::GeneralDialogType::ActivateAutoSign, data); -} - -bool AutoSignScriptProvider::isReady() const -{ - logger_->debug("[{}] signCont: {}, walletsMgr: {}, celer: {}", __func__ - , signingContainer_ && !signingContainer_->isOffline() - , walletsManager_ && walletsManager_->isReadyForTrading(), celerClient_->IsConnected()); - return signingContainer_ && !signingContainer_->isOffline() - && walletsManager_ && walletsManager_->isReadyForTrading() - && celerClient_->IsConnected(); -} - -void AutoSignScriptProvider::onAutoSignStateChanged(bs::error::ErrorCode result - , const std::string &walletId) -{ - autoSignState_ = result; - autoSignWalletId_ = QString::fromStdString(walletId); - emit autoSignStateChanged(); -} - -void AutoSignScriptProvider::setScriptLoaded(bool loaded) -{ - scriptLoaded_ = loaded; - if (!loaded) { - scriptRunner_->disable(); - } - emit scriptLoadedChanged(); -} - -void AutoSignScriptProvider::init(const QString &filename) -{ - if (filename.isEmpty()) { - return; - } - scriptLoaded_ = false; - scriptRunner_->enable(filename); - emit scriptLoadedChanged(); -} - -void AutoSignScriptProvider::deinit() -{ - scriptRunner_->disable(); - scriptLoaded_ = false; - emit scriptLoadedChanged(); -} - -void AutoSignScriptProvider::onScriptLoaded(const QString &filename) -{ - logger_->info("[AutoSignScriptProvider::onScriptLoaded] script {} loaded" - , filename.toStdString()); - scriptLoaded_ = true; - emit scriptLoadedChanged(); - - auto scripts = appSettings_->get(ApplicationSettings::aqScripts); - if (scripts.indexOf(filename) < 0) { - scripts << filename; - appSettings_->set(ApplicationSettings::aqScripts, scripts); - } - appSettings_->set(lastScript_, filename); - emit scriptHistoryChanged(); -} - -void AutoSignScriptProvider::onScriptFailed(const QString &filename, const QString &error) -{ - logger_->error("[AutoSignScriptProvider::onScriptLoaded] script {} loading failed: {}" - , filename.toStdString(), error.toStdString()); - setScriptLoaded(false); - - auto scripts = appSettings_->get(ApplicationSettings::aqScripts); - scripts.removeOne(filename); - appSettings_->set(ApplicationSettings::aqScripts, scripts); - appSettings_->reset(lastScript_); - emit scriptHistoryChanged(); -} - -void AutoSignScriptProvider::onConnectedToCeler() -{ - emit autoSignQuoteAvailabilityChanged(); -} - -void AutoSignScriptProvider::onDisconnectedFromCeler() -{ - scriptRunner_->disable(); - disableAutoSign(); - - emit autoSignQuoteAvailabilityChanged(); -} - -void AutoSignScriptProvider::setWalletsManager(std::shared_ptr &walletsMgr) -{ - walletsManager_ = walletsMgr; - scriptRunner_->setWalletsManager(walletsMgr); - - connect(walletsMgr.get(), &bs::sync::WalletsManager::walletDeleted, this - , &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged); - connect(walletsMgr.get(), &bs::sync::WalletsManager::walletAdded, this - , &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged); - connect(walletsMgr.get(), &bs::sync::WalletsManager::walletsReady, this - , &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged); - connect(walletsMgr.get(), &bs::sync::WalletsManager::walletsSynchronized, this - , &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged); - connect(walletsMgr.get(), &bs::sync::WalletsManager::newWalletAdded, this - , &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged); - connect(walletsMgr.get(), &bs::sync::WalletsManager::walletImportFinished, this - , &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged); - - emit autoSignQuoteAvailabilityChanged(); -} - -QString AutoSignScriptProvider::getAutoSignWalletName() -{ - if (!walletsManager_ || !signingContainer_) { - return QString(); - } - - const auto wallet = walletsManager_->getPrimaryWallet(); - if (!wallet) { - return QString(); - } - return QString::fromStdString(wallet->name()); -} - -QString AutoSignScriptProvider::getDefaultScriptsDir() -{ -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - return QCoreApplication::applicationDirPath() + QStringLiteral("/scripts"); -#else - return QStringLiteral("/usr/share/blocksettle/scripts"); -#endif -} - -QStringList AutoSignScriptProvider::getScripts() -{ - return appSettings_->get(ApplicationSettings::aqScripts); -} - -QString AutoSignScriptProvider::getLastScript() -{ - return appSettings_->get(lastScript_); -} - -QString AutoSignScriptProvider::getLastDir() -{ - return appSettings_->get(ApplicationSettings::LastAqDir); -} - -void AutoSignScriptProvider::setLastDir(const QString &path) -{ - appSettings_->set(ApplicationSettings::LastAqDir, QFileInfo(path).dir().absolutePath()); -} - - -AutoSignAQProvider::AutoSignAQProvider(const std::shared_ptr &logger - , UserScriptRunner *scriptRunner - , const std::shared_ptr &appSettings - , const std::shared_ptr &container - , const std::shared_ptr &celerClient - , QObject *parent) - : AutoSignScriptProvider(logger, scriptRunner, appSettings, container, celerClient, parent) -{ - auto botFileInfo = QFileInfo(getDefaultScriptsDir() + QStringLiteral("/RFQBot.qml")); - if (botFileInfo.exists() && botFileInfo.isFile()) { - auto list = appSettings_->get(ApplicationSettings::aqScripts); - if (list.indexOf(botFileInfo.absoluteFilePath()) == -1) { - list << botFileInfo.absoluteFilePath(); - } - appSettings_->set(ApplicationSettings::aqScripts, list); - const auto lastScript = appSettings_->get(ApplicationSettings::lastAqScript); - if (lastScript.isEmpty()) { - appSettings_->set(ApplicationSettings::lastAqScript, botFileInfo.absoluteFilePath()); - } - } - lastScript_ = ApplicationSettings::lastAqScript; -} - - -AutoSignRFQProvider::AutoSignRFQProvider(const std::shared_ptr &logger - , UserScriptRunner *scriptRunner - , const std::shared_ptr &appSettings - , const std::shared_ptr &container - , const std::shared_ptr &celerClient - , QObject *parent) - : AutoSignScriptProvider(logger, scriptRunner, appSettings, container, celerClient, parent) -{ - auto botFileInfo = QFileInfo(getDefaultScriptsDir() + QStringLiteral("/AutoRFQ.qml")); - if (botFileInfo.exists() && botFileInfo.isFile()) { - auto list = appSettings_->get(ApplicationSettings::aqScripts); - if (list.indexOf(botFileInfo.absoluteFilePath()) == -1) { - list << botFileInfo.absoluteFilePath(); - } - appSettings_->set(ApplicationSettings::aqScripts, list); - const auto lastScript = appSettings_->get(ApplicationSettings::CurrentRFQScript); - if (lastScript.isEmpty()) { - appSettings_->set(ApplicationSettings::CurrentRFQScript, botFileInfo.absoluteFilePath()); - } - } - lastScript_ = ApplicationSettings::CurrentRFQScript; -} diff --git a/BlockSettleUILib/Trading/AutoSignQuoteProvider.h b/BlockSettleUILib/Trading/AutoSignQuoteProvider.h deleted file mode 100644 index ba0440f71..000000000 --- a/BlockSettleUILib/Trading/AutoSignQuoteProvider.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef AUTOSIGNQUOTEPROVIDER_H -#define AUTOSIGNQUOTEPROVIDER_H - -#include "BSErrorCode.h" - -#include -#include -#include "ApplicationSettings.h" - -class BaseCelerClient; -class SignContainer; -class UserScriptRunner; -class AssetManager; -class QuoteProvider; -class MDCallbacksQt; - -namespace bs { - namespace sync { - class Wallet; - class WalletsManager; - } -} -namespace spdlog { - class logger; -} - - -class AutoSignScriptProvider : public QObject -{ - Q_OBJECT -public: - explicit AutoSignScriptProvider(const std::shared_ptr & - , UserScriptRunner * - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , QObject *parent = nullptr); - - bool isScriptLoaded() const { return scriptLoaded_; } - void setScriptLoaded(bool loaded); - - void init(const QString &filename); - void deinit(); - - static QString getDefaultScriptsDir(); - - QStringList getScripts(); - QString getLastScript(); - - QString getLastDir(); - void setLastDir(const QString &path); - - // auto sign - bs::error::ErrorCode autoSignState() const { return autoSignState_; } - QString autoSignWalletId() const { return autoSignWalletId_; } - - void disableAutoSign(); - void tryEnableAutoSign(); - - bool isReady() const; - void setWalletsManager(std::shared_ptr &); - - QString getAutoSignWalletName(); - - UserScriptRunner *scriptRunner() const { return scriptRunner_; } - -public slots: - void onSignerStateUpdated(); - void onAutoSignStateChanged(bs::error::ErrorCode result, const std::string &walletId); - - void onScriptLoaded(const QString &filename); - void onScriptFailed(const QString &filename, const QString &error); - - void onConnectedToCeler(); - void onDisconnectedFromCeler(); - -signals: - void scriptHistoryChanged(); - void autoSignStateChanged(); - void autoSignQuoteAvailabilityChanged(); - void scriptLoadedChanged(); - -protected: - std::shared_ptr appSettings_; - ApplicationSettings::Setting lastScript_{ ApplicationSettings::_last }; - std::shared_ptr logger_; - std::shared_ptr signingContainer_; - std::shared_ptr walletsManager_; - std::shared_ptr celerClient_; - UserScriptRunner *scriptRunner_{}; - - bs::error::ErrorCode autoSignState_{ bs::error::ErrorCode::AutoSignDisabled }; - QString autoSignWalletId_; - bool scriptLoaded_{ false }; - bool celerConnected_{ false }; - bool newLoaded_{ false }; -}; - -class AutoSignAQProvider : public AutoSignScriptProvider -{ - Q_OBJECT -public: - explicit AutoSignAQProvider(const std::shared_ptr & - , UserScriptRunner * - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , QObject *parent = nullptr); -}; - -class AutoSignRFQProvider : public AutoSignScriptProvider -{ - Q_OBJECT -public: - explicit AutoSignRFQProvider(const std::shared_ptr & - , UserScriptRunner * - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , QObject *parent = nullptr); -}; - -#endif // AUTOSIGNQUOTEPROVIDER_H diff --git a/BlockSettleUILib/Trading/AutoSignQuoteWidget.cpp b/BlockSettleUILib/Trading/AutoSignQuoteWidget.cpp deleted file mode 100644 index ef8b4b593..000000000 --- a/BlockSettleUILib/Trading/AutoSignQuoteWidget.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "AutoSignQuoteWidget.h" -#include "ui_AutoSignQuoteWidget.h" - -#include "AutoSignQuoteProvider.h" -#include "BSErrorCodeStrings.h" -#include "BSMessageBox.h" - -#include -#include - -namespace { - constexpr int kSelectAQFileItemIndex = 1; -} - -AutoSignQuoteWidget::AutoSignQuoteWidget(QWidget *parent) : - QWidget(parent), - ui_(new Ui::AutoSignQuoteWidget) -{ - ui_->setupUi(this); - - connect(ui_->checkBoxAQ, &ToggleSwitch::clicked, this, &AutoSignQuoteWidget::onAutoQuoteToggled); - connect(ui_->comboBoxAQScript, SIGNAL(activated(int)), this, SLOT(scriptChanged(int))); - connect(ui_->checkBoxAutoSign, &ToggleSwitch::clicked, this, &AutoSignQuoteWidget::onAutoSignToggled); - - ui_->comboBoxAQScript->setFirstItemHidden(true); -} - -void AutoSignQuoteWidget::init(const std::shared_ptr &autoSignProvider) -{ - autoSignProvider_ = autoSignProvider; - fillScriptHistory(); - onAutoSignReady(); - - ui_->checkBoxAutoSign->setChecked(autoSignProvider_->autoSignState() == bs::error::ErrorCode::NoError); - - connect(autoSignProvider_.get(), &AutoSignScriptProvider::autoSignQuoteAvailabilityChanged - , this, &AutoSignQuoteWidget::onAutoSignReady); - connect(autoSignProvider_.get(), &AutoSignScriptProvider::autoSignStateChanged - , this, &AutoSignQuoteWidget::onAutoSignStateChanged); - connect(autoSignProvider_.get(), &AutoSignScriptProvider::scriptHistoryChanged - , this, &AutoSignQuoteWidget::fillScriptHistory); - connect(autoSignProvider_.get(), &AutoSignScriptProvider::scriptLoadedChanged - , this, &AutoSignQuoteWidget::validateGUI); - - ui_->labelAutoSignWalletName->setText(autoSignProvider_->getAutoSignWalletName()); -} - -AutoSignQuoteWidget::~AutoSignQuoteWidget() = default; - -void AutoSignQuoteWidget::onAutoQuoteToggled() -{ - bool isValidScript = (ui_->comboBoxAQScript->currentIndex() > kSelectAQFileItemIndex); - if (ui_->checkBoxAQ->isChecked() && !isValidScript) { - BSMessageBox question(BSMessageBox::question - , tr("Try to enable scripting") - , tr("Script is not specified. Do you want to select a script from file?")); - const bool answerYes = (question.exec() == QDialog::Accepted); - if (answerYes) { - const auto scriptFileName = askForScript(); - if (!scriptFileName.isEmpty()) { - autoSignProvider_->init(scriptFileName); - } - } - } - - if (autoSignProvider_->isScriptLoaded()) { - autoSignProvider_->setScriptLoaded(false); - } else { - autoSignProvider_->init(ui_->comboBoxAQScript->currentData().toString()); - } - - validateGUI(); -} - -void AutoSignQuoteWidget::onAutoSignStateChanged() -{ - ui_->checkBoxAutoSign->setChecked(autoSignProvider_->autoSignState() == bs::error::ErrorCode::NoError); - if (autoSignProvider_->autoSignState() != bs::error::ErrorCode::NoError - && autoSignProvider_->autoSignState() != bs::error::ErrorCode::AutoSignDisabled) { - BSMessageBox(BSMessageBox::warning, tr("Auto Signing") - , tr("Failed to enable Auto Signing") - , bs::error::ErrorCodeToString(autoSignProvider_->autoSignState())).exec(); - } - - ui_->labelAutoSignWalletName->setText(autoSignProvider_->getAutoSignWalletName()); -} - -void AutoSignQuoteWidget::onAutoSignReady() -{ - ui_->labelAutoSignWalletName->setText(autoSignProvider_->getAutoSignWalletName()); - const bool enableWidget = autoSignProvider_->isReady(); - ui_->groupBoxAutoSign->setEnabled(enableWidget); - ui_->checkBoxAutoSign->setEnabled(enableWidget); - ui_->checkBoxAQ->setEnabled(enableWidget); - validateGUI(); -} - -void AutoSignQuoteWidget::onUserConnected(bool autoSigning, bool autoQuoting) -{ - if (autoSigning) { - ui_->checkBoxAutoSign->setChecked(true); - onAutoSignToggled(); - } - if (autoQuoting) { - ui_->checkBoxAQ->setChecked(true); - onAutoQuoteToggled(); - } -} - -void AutoSignQuoteWidget::fillScriptHistory() -{ - ui_->comboBoxAQScript->clear(); - int curIndex = 0; - ui_->comboBoxAQScript->addItem(tr("Select script...")); - ui_->comboBoxAQScript->addItem(tr("Load new script")); - const auto scripts = autoSignProvider_->getScripts(); - if (!scripts.isEmpty()) { - const auto lastScript = autoSignProvider_->getLastScript(); - for (int i = 0; i < scripts.size(); i++) { - QFileInfo fi(scripts[i]); - ui_->comboBoxAQScript->addItem(fi.fileName(), scripts[i]); - if (scripts[i] == lastScript) { - curIndex = i + kSelectAQFileItemIndex + 1; // note the "Load" row in the head - } - } - } - ui_->comboBoxAQScript->setCurrentIndex(curIndex); -} - -void AutoSignQuoteWidget::scriptChanged(int curIndex) -{ - if (curIndex < kSelectAQFileItemIndex) { - return; - } - - if (curIndex == kSelectAQFileItemIndex) { - const auto scriptFileName = askForScript(); - - if (scriptFileName.isEmpty()) { - fillScriptHistory(); - return; - } - - autoSignProvider_->init(scriptFileName); - } else { - if (autoSignProvider_->isScriptLoaded()) { - autoSignProvider_->deinit(); - } - } -} - -void AutoSignQuoteWidget::onAutoSignToggled() -{ - if (ui_->checkBoxAutoSign->isChecked()) { - autoSignProvider_->tryEnableAutoSign(); - } else { - autoSignProvider_->disableAutoSign(); - } - ui_->checkBoxAutoSign->setChecked(autoSignProvider_->autoSignState() == bs::error::ErrorCode::NoError); -} - -void AutoSignQuoteWidget::validateGUI() -{ - ui_->checkBoxAQ->setChecked(autoSignProvider_->isScriptLoaded()); -} - -QString AutoSignQuoteWidget::askForScript() -{ - auto lastDir = autoSignProvider_->getLastDir(); - if (lastDir.isEmpty()) { - lastDir = AutoSignScriptProvider::getDefaultScriptsDir(); - } - - auto path = QFileDialog::getOpenFileName(this, tr("Open script file") - , lastDir, tr("QML files (*.qml)")); - - if (!path.isEmpty()) { - autoSignProvider_->setLastDir(path); - } - - return path; -} diff --git a/BlockSettleUILib/Trading/AutoSignQuoteWidget.h b/BlockSettleUILib/Trading/AutoSignQuoteWidget.h deleted file mode 100644 index 28be5bfd7..000000000 --- a/BlockSettleUILib/Trading/AutoSignQuoteWidget.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef AUTOSIGNQUOTEWIDGET_H -#define AUTOSIGNQUOTEWIDGET_H - -#include "BSErrorCode.h" - -#include -#include - -class AutoSignScriptProvider; - -namespace Ui { -class AutoSignQuoteWidget; -} - -class AutoSignQuoteWidget : public QWidget -{ - Q_OBJECT - -public: - explicit AutoSignQuoteWidget(QWidget *parent = nullptr); - ~AutoSignQuoteWidget(); - - void init(const std::shared_ptr &autoSignQuoteProvider); - -public slots: - void onAutoSignStateChanged(); - void onAutoSignReady(); - void onUserConnected(bool, bool); - -private slots: - void fillScriptHistory(); - void scriptChanged(int curIndex); - - void onAutoQuoteToggled(); - void onAutoSignToggled(); - -private: - QString askForScript(); - void validateGUI(); - -private: - std::unique_ptr ui_; - std::shared_ptr autoSignProvider_; -}; - -#endif // AUTOSIGNQUOTEWIDGET_H diff --git a/BlockSettleUILib/Trading/AutoSignQuoteWidget.ui b/BlockSettleUILib/Trading/AutoSignQuoteWidget.ui deleted file mode 100644 index 4041a22f5..000000000 --- a/BlockSettleUILib/Trading/AutoSignQuoteWidget.ui +++ /dev/null @@ -1,388 +0,0 @@ - - - AutoSignQuoteWidget - - - - 0 - 0 - 344 - 125 - - - - - 0 - 0 - - - - Form - - - - 2 - - - QLayout::SetMinimumSize - - - 6 - - - 2 - - - 6 - - - 2 - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 0 - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 3 - - - QLayout::SetMinimumSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 0 - 0 - - - - AUTOMATED DEALING - - - true - - - - 3 - - - 2 - - - 2 - - - - - - 0 - 16 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Auto Signing - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - <> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 18 - - - - - 35 - 16777215 - - - - 50 - - - 1 - - - Qt::Horizontal - - - - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - Auto Quoting - - - - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 0 - 0 - - - - - 170 - 0 - - - - - 170 - 16777215 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 0 - 20 - - - - - - - - true - - - - 0 - 0 - - - - - 0 - 18 - - - - - 35 - 16777215 - - - - 50 - - - 1 - - - Qt::Horizontal - - - - - - - - - - - - - - - - - ToggleSwitch - QSlider -
CustomControls/ToggleSwitch.h
-
- - CustomComboBox - QComboBox -
CustomControls/CustomComboBox.h
-
-
- - -
diff --git a/BlockSettleUILib/Trading/DealerCCSettlementContainer.cpp b/BlockSettleUILib/Trading/DealerCCSettlementContainer.cpp deleted file mode 100644 index 38a4c788c..000000000 --- a/BlockSettleUILib/Trading/DealerCCSettlementContainer.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "DealerCCSettlementContainer.h" - -#include - -#include "BSErrorCodeStrings.h" -#include "CheckRecipSigner.h" -#include "SignContainer.h" -#include "SignerDefs.h" -#include "UiUtils.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWallet.h" -#include "Wallets/SyncWalletsManager.h" - -#include -#include - -using namespace bs::sync; -using namespace ArmorySigner; - -DealerCCSettlementContainer::DealerCCSettlementContainer(const std::shared_ptr &logger - , const bs::network::Order &order - , const std::string "eReqId, uint64_t lotSize - , const bs::Address &genAddr - , const std::string &ownRecvAddr - , const std::shared_ptr &xbtWallet - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &walletsMgr - , std::unique_ptr walletPurpose - , bs::UtxoReservationToken utxoRes - , bool expandTxDialogInfo) - : bs::SettlementContainer(std::move(utxoRes), std::move(walletPurpose), expandTxDialogInfo) - , logger_(logger) - , order_(order) - , lotSize_(lotSize) - , genesisAddr_(genAddr) - , delivery_(order.side == bs::network::Side::Sell) - , xbtWallet_(xbtWallet) - , signingContainer_(container) - , walletsMgr_(walletsMgr) - , ownRecvAddr_(bs::Address::fromAddressString(ownRecvAddr)) - , orderId_(QString::fromStdString(order.clOrderId)) - , signer_(armory) -{ - if (lotSize == 0) { - throw std::logic_error("invalid lotSize"); - } - - ccWallet_ = walletsMgr->getCCWallet(order.product); - if (!ccWallet_) { - throw std::logic_error("can't find CC wallet"); - } - - if (!txReqData_.ParseFromString(BinaryData::CreateFromHex(order.reqTransaction).toBinStr())) { - throw std::invalid_argument("invalid requester transaction"); - } - - init(armory.get()); - settlWallet_ = armory->instantiateWallet(order_.clOrderId); - if (!settlWallet_) { - throw std::runtime_error("can't register settlement wallet in armory"); - } - - connect(this, &DealerCCSettlementContainer::genAddressVerified, this - , &DealerCCSettlementContainer::onGenAddressVerified, Qt::QueuedConnection); -} - -DealerCCSettlementContainer::~DealerCCSettlementContainer() -{ - settlWallet_->unregister(); - cleanup(); -} - -bs::sync::PasswordDialogData DealerCCSettlementContainer::toPasswordDialogData(QDateTime timestamp) const -{ - bs::sync::PasswordDialogData dialogData = SettlementContainer::toPasswordDialogData(timestamp); - dialogData.setValue(PasswordDialogData::IsDealer, true); - dialogData.setValue(PasswordDialogData::Market, "CC"); - dialogData.setValue(PasswordDialogData::AutoSignCategory, static_cast(bs::signer::AutoSignCategory::SettlementDealer)); - dialogData.setValue(PasswordDialogData::LotSize, static_cast(lotSize_)); - - if (side() == bs::network::Side::Sell) { - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Delivery")); - } - else { - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Payment")); - } - - // rfq details - dialogData.setValue(PasswordDialogData::Price, UiUtils::displayPriceCC(price())); - dialogData.setValue(PasswordDialogData::Quantity, order_.quantity); - - // tx details - if (side() == bs::network::Side::Buy) { - dialogData.setValue(PasswordDialogData::TxInputProduct, UiUtils::XbtCurrency); - } - else { - dialogData.setValue(PasswordDialogData::TxInputProduct, product()); - } - - // settlement details - dialogData.setValue(PasswordDialogData::DeliveryUTXOVerified, genAddrVerified_); - dialogData.setValue(PasswordDialogData::SigningAllowed, genAddrVerified_); - - dialogData.setValue(PasswordDialogData::RecipientsListVisible, true); - dialogData.setValue(PasswordDialogData::InputsListVisible, true); - - return dialogData; -} - -void DealerCCSettlementContainer::onZCReceived(const std::string & - , const std::vector &entries) -{ - if (expectedTxId_.empty()) { - return; - } - for (const auto &entry : entries) { - if (entry.txHash == expectedTxId_) { - emit completed(id()); - break; - } - } -} - -bool DealerCCSettlementContainer::startSigning(QDateTime timestamp) -{ - if (!ccWallet_ || !xbtWallet_) { - logger_->error("[DealerCCSettlementContainer::accept] failed to validate counterparty's TX - aborting"); - sendFailed(); - return false; - } - - const auto &cbTx = [this, handle = validityFlag_.handle(), logger = logger_] - (bs::error::ErrorCode result, const BinaryData &signedTX) - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "failed to sign TX half, already destroyed"); - return; - } - - signId_ = 0; - - if (result == bs::error::ErrorCode::NoError) { - emit signTxRequest(orderId_, signedTX.toHexStr()); - try { - Codec_SignerState::SignerState state; - if (!state.ParseFromString(signedTX.toBinStr())) { - throw std::runtime_error("invalid signed state"); - } - Signer signer(state); - expectedTxId_ = signer.getTxId(); - } - catch (const std::exception &e) { - SPDLOG_LOGGER_ERROR(logger, "failed to parse signer state: {}", e.what()); - } - // FIXME: Does not work as expected as signedTX txid is different from combined txid - //wallet_->setTransactionComment(signedTX, txComment()); - } - else if (result == bs::error::ErrorCode::TxCancelled) { - emit cancelTrade(orderId_.toStdString()); - } - else { - SPDLOG_LOGGER_ERROR(logger_, "failed to sign TX half: {}" - , bs::error::ErrorCodeToString(result).toStdString()); - emit error(id(), result, tr("TX half signing failed: %1") - .arg(bs::error::ErrorCodeToString(result))); - sendFailed(); - } - }; - - { - txReq_.armorySigner_.deserializeState(txReqData_); - auto inputs = bs::UtxoReservation::instance()->get(utxoRes_.reserveId()); - for (auto& input : inputs) { - try { - txReq_.armorySigner_.populateUtxo(input); - } - catch (const std::exception&) { - continue; - } - } - } - - txReq_.walletIds.clear(); - for (unsigned i=0; i < txReq_.armorySigner_.getTxInCount(); i++) { - const auto& utxo = txReq_.armorySigner_.getSpender(i)->getUtxo(); - const auto addr = bs::Address::fromUTXO(utxo); - const auto wallet = walletsMgr_->getWalletByAddress(addr); - if (!wallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find wallet from UTXO"); - continue; - } - txReq_.walletIds.push_back(wallet->walletId()); - } - - //Waiting for TX half signing... - SPDLOG_LOGGER_DEBUG(logger_, "signing with {} inputs", txReq_.armorySigner_.getTxInCount()); - signId_ = signingContainer_->signSettlementPartialTXRequest(txReq_, toPasswordDialogData(timestamp), cbTx); - return ( signId_ > 0); -} - -void DealerCCSettlementContainer::activate() -{ - settlWallet_->registerAddresses({ ownRecvAddr_.prefixed() }, true); - try { - signer_.deserializeState(txReqData_); - foundRecipAddr_ = signer_.findRecipAddress(ownRecvAddr_, [this](uint64_t value, uint64_t valReturn, uint64_t valInput) { - if ((order_.side == bs::network::Side::Buy) && qFuzzyCompare(order_.quantity, value / lotSize_)) { - amountValid_ = true; //valInput == (value + valReturn); - } - else if ((order_.side == bs::network::Side::Sell) && - (value == static_cast(std::llround(order_.quantity * order_.price * BTCNumericTypes::BalanceDivider)))) { - amountValid_ = true; //valInput > (value + valReturn); - } - }); - } - catch (const std::exception &e) { - logger_->error("Signer deser exc: {}", e.what()); - emit genAddressVerified(false); - return; - } - - if (!foundRecipAddr_ || !amountValid_) { - logger_->warn("[DealerCCSettlementContainer::activate] requester's TX verification failed: {}/{}" - , foundRecipAddr_, amountValid_); - ccWallet_ = nullptr; - xbtWallet_ = nullptr; - emit genAddressVerified(false); - } - else if (order_.side == bs::network::Side::Buy) { - //Waiting for genesis address verification to complete... - const auto &cbHasInput = [this, handle = validityFlag_.handle()](bool has) { - if (!handle.isValid()) { - return; - } - emit genAddressVerified(has); - }; - signer_.hasInputAddress(genesisAddr_, cbHasInput, lotSize_); - } - else { - emit genAddressVerified(true); - } - - startTimer(kWaitTimeoutInSec); -} - -void DealerCCSettlementContainer::onGenAddressVerified(bool addressVerified) -{ - genAddrVerified_ = addressVerified; - if (!addressVerified) { - logger_->warn("[DealerCCSettlementContainer::onGenAddressVerified] " - "counterparty's TX is unverified"); - emit error(id(), bs::error::ErrorCode::InternalError - , tr("Failed to verify counterparty's transaction")); - ccWallet_ = nullptr; - xbtWallet_ = nullptr; - } - - bs::sync::PasswordDialogData pd; - pd.setValue(PasswordDialogData::SettlementId, id()); - pd.setValue(PasswordDialogData::DeliveryUTXOVerified, addressVerified); - pd.setValue(PasswordDialogData::SigningAllowed, addressVerified); - - signingContainer_->updateDialogData(pd); -} - -bool DealerCCSettlementContainer::cancel() -{ - if (signId_ == 0) { - signingContainer_->CancelSignTx(BinaryData::fromString(id())); - } - - SettlementContainer::releaseUtxoRes(); - - emit timerExpired(); - return true; -} - -std::string DealerCCSettlementContainer::txComment() -{ - return std::string(bs::network::Side::toString(order_.side)) - + " " + order_.security + " @ " + std::to_string(order_.price); -} - -void DealerCCSettlementContainer::sendFailed() -{ - SettlementContainer::releaseUtxoRes(); - emit failed(id()); -} diff --git a/BlockSettleUILib/Trading/DealerCCSettlementContainer.h b/BlockSettleUILib/Trading/DealerCCSettlementContainer.h deleted file mode 100644 index 68711784a..000000000 --- a/BlockSettleUILib/Trading/DealerCCSettlementContainer.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __DEALER_CC_SETTLEMENT_CONTAINER_H__ -#define __DEALER_CC_SETTLEMENT_CONTAINER_H__ - -#include -#include "AddressVerificator.h" -#include "CheckRecipSigner.h" -#include "SettlementContainer.h" -#include "UtxoReservationToken.h" -#include "CoreWallet.h" - -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - namespace hd { - class Wallet; - } - class Wallet; - class WalletsManager; - } -} -class ArmoryConnection; -class SignContainer; - - -class DealerCCSettlementContainer : public bs::SettlementContainer - , public ArmoryCallbackTarget -{ - Q_OBJECT -public: - DealerCCSettlementContainer(const std::shared_ptr &, const bs::network::Order & - , const std::string "eReqId, uint64_t lotSize - , const bs::Address &genAddr - , const std::string &ownRecvAddr - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &walletsMgr - , std::unique_ptr walletPurpose - , bs::UtxoReservationToken utxoRes - , bool expandTxDialogInfo); - ~DealerCCSettlementContainer() override; - - bool startSigning(QDateTime timestamp); - bool cancel() override; - - void activate() override; - void deactivate() override { stopTimer(); } - - std::string id() const override { return orderId_.toStdString(); } - bs::network::Asset::Type assetType() const override { return order_.assetType; } - std::string security() const override { return order_.security; } - std::string product() const override { return order_.product; } - bs::network::Side::Type side() const override { return order_.side; } - double quantity() const override { return order_.quantity; } - double price() const override { return order_.price; } - double amount() const override { return quantity(); } - bs::sync::PasswordDialogData toPasswordDialogData(QDateTime timestamp) const override; - - bool foundRecipAddr() const { return foundRecipAddr_; } - bool isAmountValid() const { return amountValid_; } - - bool isDelivery() const { return delivery_; } - -signals: - void signTxRequest(const QString &orderId, std::string txData); - void genAddressVerified(bool result); - - void cancelTrade(const std::string& clOrdId); - -private slots: - void onGenAddressVerified(bool result); - -private: - std::string txComment(); - void sendFailed(); - void onZCReceived(const std::string &, const std::vector &) override; - -private: - std::shared_ptr logger_; - const bs::network::Order order_; - const uint64_t lotSize_; - const bs::Address genesisAddr_; - const bool delivery_; - std::shared_ptr xbtWallet_; - std::shared_ptr signingContainer_; - std::shared_ptr walletsMgr_; - Codec_SignerState::SignerState txReqData_; - const bs::Address ownRecvAddr_; - const QString orderId_; - bool foundRecipAddr_ = false; - bool amountValid_ = false; - bool genAddrVerified_ = true; - unsigned int signId_ = 0; - bs::CheckRecipSigner signer_; - bs::core::wallet::TXSignRequest txReq_; - bool isGetAddressValid_ = false; - std::shared_ptr ccWallet_; - std::shared_ptr settlWallet_; - BinaryData expectedTxId_; -}; - -#endif // __DEALER_CC_SETTLEMENT_CONTAINER_H__ diff --git a/BlockSettleUILib/Trading/DealerXBTSettlementContainer.cpp b/BlockSettleUILib/Trading/DealerXBTSettlementContainer.cpp deleted file mode 100644 index 166897cd2..000000000 --- a/BlockSettleUILib/Trading/DealerXBTSettlementContainer.cpp +++ /dev/null @@ -1,539 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "DealerXBTSettlementContainer.h" - -#include "AuthAddressManager.h" -#include "CheckRecipSigner.h" -#include "CurrencyPair.h" -#include "QuoteProvider.h" -#include "TradesUtils.h" -#include "TradesVerification.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "WalletSignerContainer.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" - -#include - -#include - -Q_DECLARE_METATYPE(AddressVerificationState) - -using namespace bs::sync; - -DealerXBTSettlementContainer::DealerXBTSettlementContainer(const std::shared_ptr &logger - , const bs::network::Order &order - , const std::shared_ptr &walletsMgr - , const std::shared_ptr &xbtWallet - , const std::shared_ptr "eProvider - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &authAddrMgr - , const bs::Address &authAddr - , const std::vector &utxosPayinFixed - , const bs::Address &recvAddr - , const std::shared_ptr &utxoReservationManager - , std::unique_ptr walletPurpose - , bs::UtxoReservationToken utxoRes - , bool expandTxDialogInfo - , uint64_t tier1XbtLimit ) - : bs::SettlementContainer(std::move(utxoRes), std::move(walletPurpose), expandTxDialogInfo) - , order_(order) - , weSellXbt_((order.side == bs::network::Side::Buy) != (order.product == bs::network::XbtCurrency)) - , amount_((order.product != bs::network::XbtCurrency) ? order.quantity / order.price : order.quantity) - , logger_(logger) - , armory_(armory) - , walletsMgr_(walletsMgr) - , xbtWallet_(xbtWallet) - , signContainer_(container) - , authAddrMgr_(authAddrMgr) - , utxosPayinFixed_(utxosPayinFixed) - , recvAddr_(recvAddr) - , authAddr_(authAddr) - , utxoReservationManager_(utxoReservationManager) -{ - qRegisterMetaType(); - - CurrencyPair cp(security()); - fxProd_ = cp.ContraCurrency(bs::network::XbtCurrency); - - if (!xbtWallet_) { - throw std::runtime_error("no wallet"); - } - - auto qn = quoteProvider->getSubmittedXBTQuoteNotification(order.settlementId); - if (qn.authKey.empty() || qn.reqAuthKey.empty() || qn.settlementId.empty()) { - throw std::invalid_argument("failed to get submitted QN for " + order.quoteId); - } - - const auto xbtAmount = bs::XBTAmount(amount_); - requesterAddressShouldBeVerified_ = xbtAmount > bs::XBTAmount(tier1XbtLimit); - - // BST-2545: Use price as it see Genoa (and it computes it as ROUNDED_CCY / XBT) - const auto actualXbtPrice = UiUtils::actualXbtPrice(xbtAmount, price()); - - auto side = order.product == bs::network::XbtCurrency ? order.side : bs::network::Side::invert(order.side); - comment_ = fmt::format("{} {} @ {}", bs::network::Side::toString(side) - , order.security, UiUtils::displayPriceXBT(actualXbtPrice).toStdString()); - authKey_ = BinaryData::CreateFromHex(qn.authKey); - reqAuthKey_ = BinaryData::CreateFromHex(qn.reqAuthKey); - if (authKey_.empty() || reqAuthKey_.empty()) { - throw std::runtime_error("missing auth key"); - } - - init(armory_.get()); - settlementIdHex_ = qn.settlementId; - settlementId_ = BinaryData::CreateFromHex(qn.settlementId); - settlWallet_ = armory_->instantiateWallet(settlementIdHex_); - if (!settlWallet_) { - throw std::runtime_error("can't register settlement wallet in armory"); - } - - connect(signContainer_.get(), &SignContainer::TXSigned, this, &DealerXBTSettlementContainer::onTXSigned); -} - -bool DealerXBTSettlementContainer::cancel() -{ - if (payinSignId_ != 0 || payoutSignId_ != 0) { - signContainer_->CancelSignTx(settlementId_); - } - - releaseUtxoRes(); - - SPDLOG_LOGGER_DEBUG(logger_, "cancel on a trade : {}", settlementIdHex_); - - emit timerExpired(); - return true; -} - -DealerXBTSettlementContainer::~DealerXBTSettlementContainer() -{ - settlWallet_->unregister(); - cleanup(); -} - -bs::sync::PasswordDialogData DealerXBTSettlementContainer::toPasswordDialogData(QDateTime timestamp) const -{ - bs::sync::PasswordDialogData dialogData = SettlementContainer::toPasswordDialogData(timestamp); - - dialogData.setValue(PasswordDialogData::IsDealer, true); - dialogData.setValue(PasswordDialogData::Market, "XBT"); - dialogData.setValue(PasswordDialogData::AutoSignCategory, static_cast(bs::signer::AutoSignCategory::SettlementDealer)); - - // rfq details - QString qtyProd = UiUtils::XbtCurrency; - - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Pay-In")); - dialogData.setValue(PasswordDialogData::Price, UiUtils::displayPriceXBT(price())); - dialogData.setValue(PasswordDialogData::FxProduct, fxProd_); - - bool isFxProd = (product() != bs::network::XbtCurrency); - - if (isFxProd) { - dialogData.setValue(PasswordDialogData::Quantity, tr("%1 %2") - .arg(UiUtils::displayAmountForProduct(quantity(), QString::fromStdString(fxProd_), bs::network::Asset::Type::SpotXBT)) - .arg(QString::fromStdString(fxProd_))); - - dialogData.setValue(PasswordDialogData::TotalValue, tr("%1 XBT") - .arg(UiUtils::displayAmount(quantity() / price()))); - } else { - dialogData.setValue(PasswordDialogData::Quantity, tr("%1 XBT") - .arg(UiUtils::displayAmount(amount()))); - - dialogData.setValue(PasswordDialogData::TotalValue, tr("%1 %2") - .arg(UiUtils::displayAmountForProduct(amount() * price(), QString::fromStdString(fxProd_), bs::network::Asset::Type::SpotXBT)) - .arg(QString::fromStdString(fxProd_))); - } - - // settlement details - dialogData.setValue(PasswordDialogData::SettlementId, settlementId_.toHexStr()); - dialogData.setValue(PasswordDialogData::SettlementAddress, settlAddr_.display()); - - dialogData.setValue(PasswordDialogData::RequesterAuthAddress, - bs::Address::fromPubKey(reqAuthKey_, AddressEntryType_P2WPKH).display()); - dialogData.setValue(PasswordDialogData::RequesterAuthAddressVerified, requestorAddressState_ == AddressVerificationState::Verified); - dialogData.setValue(PasswordDialogData::SigningAllowed, requestorAddressState_ == AddressVerificationState::Verified); - - dialogData.setValue(PasswordDialogData::ResponderAuthAddress, - bs::Address::fromPubKey(authKey_, AddressEntryType_P2WPKH).display()); - dialogData.setValue(PasswordDialogData::ResponderAuthAddressVerified, true); - - // tx details - dialogData.setValue(PasswordDialogData::TxInputProduct, UiUtils::XbtCurrency); - - return dialogData; -} - -void DealerXBTSettlementContainer::activate() -{ - startTimer(kWaitTimeoutInSec); - - addrVerificator_ = std::make_shared(logger_, armory_ - , [this, handle = validityFlag_.handle()](const bs::Address &address, AddressVerificationState state) - { - QMetaObject::invokeMethod(qApp, [this, handle, address, state] { - if (!handle.isValid()) { - return; - } - - SPDLOG_LOGGER_INFO(logger_, "counterparty's address verification {} for {}" - , to_string(state), address.display()); - requestorAddressState_ = state; - - if (state == AddressVerificationState::Verified) { - // we verify only requester's auth address - bs::sync::PasswordDialogData dialogData; - dialogData.setValue(PasswordDialogData::RequesterAuthAddressVerified, true); - dialogData.setValue(PasswordDialogData::SettlementId, settlementId_.toHexStr()); - dialogData.setValue(PasswordDialogData::SigningAllowed, true); - - signContainer_->updateDialogData(dialogData); - } - }); - }); - - addrVerificator_->SetBSAddressList(authAddrMgr_->GetBSAddresses()); - - if (requesterAddressShouldBeVerified_) { - const auto reqAuthAddrSW = bs::Address::fromPubKey(reqAuthKey_, AddressEntryType_P2WPKH); - addrVerificator_->addAddress(reqAuthAddrSW); - addrVerificator_->startAddressVerification(); - } else { - requestorAddressState_ = AddressVerificationState::Verified; - } - - const auto &authLeaf = walletsMgr_->getAuthWallet(); - signContainer_->setSettlAuthAddr(authLeaf->walletId(), settlementId_, authAddr_); -} - -void DealerXBTSettlementContainer::deactivate() -{ - stopTimer(); -} - -void DealerXBTSettlementContainer::onTXSigned(unsigned int idReq, BinaryData signedTX - , bs::error::ErrorCode errCode, std::string errMsg) -{ - if (payoutSignId_ && (payoutSignId_ == idReq)) { - payoutSignId_ = 0; - - if (errCode == bs::error::ErrorCode::TxCancelled) { - emit cancelTrade(settlementIdHex_); - return; - } - - if ((errCode != bs::error::ErrorCode::NoError) || signedTX.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "failed to sign pay-out: {} ({})", int(errCode), errMsg); - failWithErrorText(tr("Failed to sign pay-out"), errCode); - return; - } - - bs::tradeutils::PayoutVerifyArgs verifyArgs; - verifyArgs.signedTx = signedTX; - verifyArgs.settlAddr = settlAddr_; - verifyArgs.usedPayinHash = expectedPayinHash_; - verifyArgs.amount = bs::XBTAmount(amount_); - auto verifyResult = bs::tradeutils::verifySignedPayout(verifyArgs); - if (!verifyResult.success) { - SPDLOG_LOGGER_ERROR(logger_, "payout verification failed: {}", verifyResult.errorMsg); - failWithErrorText(tr("Payin verification failed"), errCode); - return; - } - - for (const auto &leaf : xbtWallet_->getGroup(bs::sync::hd::Wallet::getXBTGroupType())->getLeaves()) { - leaf->setTransactionComment(signedTX, comment_); - } -// settlWallet_->setTransactionComment(signedTX, comment_); //TODO: implement later - - SPDLOG_LOGGER_DEBUG(logger_, "signed payout: {}", signedTX.toHexStr()); - - emit sendSignedPayoutToPB(settlementIdHex_, signedTX); - - // ok. there is nothing this container could/should do -// emit completed(id()); - } - - if ((payinSignId_ != 0) && (payinSignId_ == idReq)) { - payinSignId_ = 0; - - if (errCode == bs::error::ErrorCode::TxCancelled) { - emit cancelTrade(settlementIdHex_); - return; - } - - if ((errCode != bs::error::ErrorCode::NoError) || signedTX.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "Failed to sign pay-in: {} ({})", (int)errCode, errMsg); - if (errCode == bs::error::ErrorCode::TxSpendLimitExceed) { - failWithErrorText(tr("Auto-signing (and auto-quoting) have been disabled" - " as your limit has been hit or elapsed"), errCode); - } - else { - failWithErrorText(tr("Failed to sign Pay-in"), errCode); - } - return; - } - - try { - const Tx tx(signedTX); - if (!tx.isInitialized()) { - throw std::runtime_error("uninited TX"); - } - - if (tx.getThisHash() != expectedPayinHash_) { - failWithErrorText(tr("payin hash mismatch"), bs::error::ErrorCode::TxInvalidRequest); - return; - } - } catch (const std::exception &e) { - failWithErrorText(tr("invalid signed pay-in"), bs::error::ErrorCode::TxInvalidRequest); - return; - } - - for (const auto &leaf : xbtWallet_->getGroup(bs::sync::hd::Wallet::getXBTGroupType())->getLeaves()) { - leaf->setTransactionComment(signedTX, comment_); - } -// settlWallet_->setTransactionComment(signedTX, comment_); //TODO: implement later - - emit sendSignedPayinToPB(settlementIdHex_, signedTX); - logger_->debug("[DealerXBTSettlementContainer::onTXSigned] Payin sent"); - - // ok. there is nothing this container could/should do -// emit completed(id()); - } -} - -void DealerXBTSettlementContainer::onZCReceived(const std::string & - , const std::vector &entries) -{ - const auto &cbTX = [this](const Tx &tx) - { - if (tx.getNumTxIn() != 1) { // not a pay-out - return; - } - const auto &txIn = tx.getTxInCopy(0); - const auto &op = txIn.getOutPoint(); - if (op.getTxHash() != expectedPayinHash_) { - return; // not our pay-out - } - const auto &serializedTx = tx.serialize(); - const auto &buyAuthKey = weSellXbt_ ? reqAuthKey_ : authKey_; - const auto &sellAuthKey = weSellXbt_ ? authKey_ : reqAuthKey_; - const auto &result = bs::TradesVerification::verifySignedPayout(serializedTx - , buyAuthKey.toHexStr(), sellAuthKey.toHexStr(), expectedPayinHash_ - , bs::XBTAmount(amount_).GetValue(), utxoReservationManager_->feeRatePb() - , settlementIdHex_, settlAddr_.display()); - if (result->success) { - emit completed(id()); - } - else { - emit failed(id()); - } - }; - - for (const auto &entry : entries) { - if (entry.walletIds.find(settlementIdHex_) == entry.walletIds.end()) { - continue; - } - if (entry.txHash == expectedPayinHash_) { - continue; // not interested in pay-in - } - armory_->getTxByHash(entry.txHash, cbTX, true); - } -} - -void DealerXBTSettlementContainer::onUnsignedPayinRequested(const std::string& settlementId) -{ - if (settlementIdHex_ != settlementId) { - // ignore - return; - } - - if (!weSellXbt_) { - SPDLOG_LOGGER_ERROR(logger_, "dealer is buying. Should not create unsigned payin on {}", settlementIdHex_); - return; - } - - bs::tradeutils::PayinArgs args; - initTradesArgs(args, settlementId); - args.fixedInputs = utxosPayinFixed_; - - const auto xbtGroup = xbtWallet_->getGroup(xbtWallet_->getXBTGroupType()); - if (!xbtWallet_->canMixLeaves()) { - assert(walletPurpose_); - const auto leaf = xbtGroup->getLeaf(*walletPurpose_); - args.inputXbtWallets.push_back(leaf); - } - else { - for (const auto &leaf : xbtGroup->getLeaves()) { - args.inputXbtWallets.push_back(leaf); - } - } - - args.utxoReservation = bs::UtxoReservation::instance(); - - auto payinCb = bs::tradeutils::PayinResultCb([this, handle = validityFlag_.handle()] - (bs::tradeutils::PayinResult result) - { - QMetaObject::invokeMethod(qApp, [this, handle, result = std::move(result)] { - if (!handle.isValid()) { - return; - } - - if (!result.success) { - SPDLOG_LOGGER_ERROR(logger_, "creating payin request failed: {}", result.errorMsg); - failWithErrorText(tr("creating payin request failed"), bs::error::ErrorCode::InternalError); - return; - } - - settlAddr_ = result.settlementAddr; - settlWallet_->registerAddresses({ settlAddr_.prefixed() }, true); - - unsignedPayinRequest_ = std::move(result.signRequest); - // Reserve only automatic UTXO selection - if (utxosPayinFixed_.empty()) { - utxoRes_ = utxoReservationManager_->makeNewReservation(unsignedPayinRequest_.getInputs(nullptr), id()); - } - - emit sendUnsignedPayinToPB(settlementIdHex_ - , bs::network::UnsignedPayinData{ unsignedPayinRequest_.serializeState().SerializeAsString() }); - - const auto &authLeaf = walletsMgr_->getAuthWallet(); - signContainer_->setSettlCP(authLeaf->walletId(), result.payinHash, settlementId_, reqAuthKey_); - }); - }); - - bs::tradeutils::createPayin(std::move(args), std::move(payinCb)); -} - -void DealerXBTSettlementContainer::onSignedPayoutRequested(const std::string& settlementId - , const BinaryData& payinHash, QDateTime timestamp) -{ - if (settlementIdHex_ != settlementId) { - // ignore - return; - } - - startTimer(kWaitTimeoutInSec); - - if (weSellXbt_) { - SPDLOG_LOGGER_ERROR(logger_, "dealer is selling. Should not sign payout on {}", settlementIdHex_); - return; - } - - expectedPayinHash_ = payinHash; - - bs::tradeutils::PayoutArgs args; - initTradesArgs(args, settlementId); - args.payinTxId = payinHash; - args.recvAddr = recvAddr_; - - const auto xbtGroup = xbtWallet_->getGroup(xbtWallet_->getXBTGroupType()); - if (!xbtWallet_->canMixLeaves()) { - assert(walletPurpose_); - const auto leaf = xbtGroup->getLeaf(*walletPurpose_); - args.outputXbtWallet = leaf; - } - else { - args.outputXbtWallet = xbtGroup->getLeaves().at(0); - } - - auto payoutCb = bs::tradeutils::PayoutResultCb([this, payinHash, timestamp, handle = validityFlag_.handle()] - (bs::tradeutils::PayoutResult result) - { - QMetaObject::invokeMethod(qApp, [this, payinHash, handle, timestamp, result = std::move(result)] { - if (!handle.isValid()) { - return; - } - - if (!result.success) { - SPDLOG_LOGGER_ERROR(logger_, "creating payout failed: {}", result.errorMsg); - failWithErrorText(tr("creating payout failed"), bs::error::ErrorCode::InternalError); - return; - } - - settlAddr_ = result.settlementAddr; - settlWallet_->registerAddresses({ settlAddr_.prefixed() }, true); - - bs::sync::PasswordDialogData dlgData = toPayOutTxDetailsPasswordDialogData(result.signRequest, timestamp); - dlgData.setValue(PasswordDialogData::IsDealer, true); - dlgData.setValue(PasswordDialogData::Market, "XBT"); - dlgData.setValue(PasswordDialogData::SettlementId, settlementId_.toHexStr()); - dlgData.setValue(PasswordDialogData::AutoSignCategory, static_cast(bs::signer::AutoSignCategory::SettlementDealer)); - - //note: signRequest should propably be a shared_ptr - auto signerObj = result.signRequest; - payoutSignId_ = signContainer_->signSettlementPayoutTXRequest(signerObj - , { settlementId_, reqAuthKey_, true }, dlgData); - }); - }); - bs::tradeutils::createPayout(std::move(args), std::move(payoutCb)); - - const auto &authLeaf = walletsMgr_->getAuthWallet(); - signContainer_->setSettlCP(authLeaf->walletId(), payinHash, settlementId_, reqAuthKey_); -} - -void DealerXBTSettlementContainer::onSignedPayinRequested(const std::string& settlementId - , const BinaryData &, const BinaryData& payinHash, QDateTime timestamp) -{ - if (settlementIdHex_ != settlementId) { - // ignore - return; - } - - if (payinHash.empty()) { - failWithErrorText(tr("Invalid Sign Pay-In request"), bs::error::ErrorCode::InternalError); - return; - } - - expectedPayinHash_ = payinHash; - - startTimer(kWaitTimeoutInSec); - - SPDLOG_LOGGER_DEBUG(logger_, "start sign payin: {}", settlementId); - - if (!weSellXbt_) { - SPDLOG_LOGGER_ERROR(logger_, "dealer is buying. Should not sign payin on {}", settlementIdHex_); - return; - } - - if (!unsignedPayinRequest_.isValid()) { - SPDLOG_LOGGER_ERROR(logger_, "unsigned payin request is invalid: {}", settlementIdHex_); - failWithErrorText(tr("Invalid unsigned pay-in"), bs::error::ErrorCode::InternalError); - return; - } - - bs::sync::PasswordDialogData dlgData = toPasswordDialogData(timestamp); - dlgData.setValue(PasswordDialogData::SettlementPayInVisible, true); - - payinSignId_ = signContainer_->signSettlementTXRequest(unsignedPayinRequest_, dlgData, SignContainer::TXSignMode::Full); -} - -void DealerXBTSettlementContainer::failWithErrorText(const QString &errorMessage, bs::error::ErrorCode code) -{ - SettlementContainer::releaseUtxoRes(); - - emit cancelTrade(settlementIdHex_); - - emit error(id(), code, errorMessage); - emit failed(id()); -} - -void DealerXBTSettlementContainer::initTradesArgs(bs::tradeutils::Args &args, const std::string &settlementId) -{ - args.amount = bs::XBTAmount{amount_}; - args.settlementId = BinaryData::CreateFromHex(settlementId); - args.ourAuthAddress = authAddr_; - args.cpAuthPubKey = reqAuthKey_; - args.walletsMgr = walletsMgr_; - args.armory = armory_; - args.signContainer = signContainer_; - args.feeRatePb_ = utxoReservationManager_->feeRatePb(); -} diff --git a/BlockSettleUILib/Trading/DealerXBTSettlementContainer.h b/BlockSettleUILib/Trading/DealerXBTSettlementContainer.h deleted file mode 100644 index 364de5362..000000000 --- a/BlockSettleUILib/Trading/DealerXBTSettlementContainer.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __DEALER_XBT_SETTLEMENT_CONTAINER_H__ -#define __DEALER_XBT_SETTLEMENT_CONTAINER_H__ - -#include "AddressVerificator.h" -#include "BSErrorCode.h" -#include "SettlementContainer.h" - -#include -#include - -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - namespace hd { - class Wallet; - } - class SettlementWallet; - class Wallet; - class WalletsManager; - } - namespace tradeutils { - struct Args; - } - class UTXOReservationManager; -} -class ArmoryConnection; -class AuthAddressManager; -class QuoteProvider; -class WalletSignerContainer; - - -class DealerXBTSettlementContainer : public bs::SettlementContainer - , public ArmoryCallbackTarget -{ - Q_OBJECT -public: - DealerXBTSettlementContainer(const std::shared_ptr & - , const bs::network::Order & - , const std::shared_ptr & - , const std::shared_ptr &xbtWallet - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &authAddrMgr - , const bs::Address &authAddr - , const std::vector &utxosPayinFixed - , const bs::Address &recvAddr - , const std::shared_ptr &utxoReservationManager - , std::unique_ptr walletPurpose - , bs::UtxoReservationToken utxoRes - , bool expandTxDialogInfo - , uint64_t tier1XbtLimit); - ~DealerXBTSettlementContainer() override; - - bool cancel() override; - - void activate() override; - void deactivate() override; - - std::string id() const override { return order_.settlementId; } - bs::network::Asset::Type assetType() const override { return order_.assetType; } - std::string security() const override { return order_.security; } - std::string product() const override { return order_.product; } - bs::network::Side::Type side() const override { return order_.side; } - double quantity() const override { return order_.quantity; } - double price() const override { return order_.price; } - double amount() const override { return amount_; } - bs::sync::PasswordDialogData toPasswordDialogData(QDateTime timestamp) const override; - -public slots: - void onUnsignedPayinRequested(const std::string& settlementId); - void onSignedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash - , QDateTime timestamp); - void onSignedPayinRequested(const std::string& settlementId, const BinaryData &unsignedPayin - , const BinaryData &payinHash, QDateTime timestamp); - -signals: - void sendUnsignedPayinToPB(const std::string& settlementId, const bs::network::UnsignedPayinData& unsignedPayinData); - void sendSignedPayinToPB(const std::string& settlementId, const BinaryData& signedPayin); - void sendSignedPayoutToPB(const std::string& settlementId, const BinaryData& signedPayout); - - void cancelTrade(const std::string& settlementId); - -private slots: - void onTXSigned(unsigned int id, BinaryData signedTX, bs::error::ErrorCode, std::string errMsg); - -private: - void failWithErrorText(const QString& error, bs::error::ErrorCode code); - - void initTradesArgs(bs::tradeutils::Args &args, const std::string &settlementId); - - void onZCReceived(const std::string &, const std::vector &) override; - -private: - const bs::network::Order order_; - std::string fxProd_; - const bool weSellXbt_; - std::string comment_; - const double amount_; - - std::shared_ptr logger_; - std::shared_ptr armory_; - std::shared_ptr walletsMgr_; - std::shared_ptr xbtWallet_; - std::shared_ptr addrVerificator_; - std::shared_ptr signContainer_; - std::shared_ptr authAddrMgr_; - std::shared_ptr utxoReservationManager_; - - AddressVerificationState requestorAddressState_ = AddressVerificationState::VerificationFailed; - bs::Address settlAddr_; - - std::string settlementIdHex_; - BinaryData settlementId_; - BinaryData authKey_; - BinaryData reqAuthKey_; - - bs::core::wallet::TXSignRequest unsignedPayinRequest_; - - unsigned int payinSignId_ = 0; - unsigned int payoutSignId_ = 0; - - BinaryData expectedPayinHash_; - - std::vector utxosPayinFixed_; - bs::Address recvAddr_; - bs::Address authAddr_; - - std::shared_ptr settlWallet_; - bool requesterAddressShouldBeVerified_ = true; -}; - -#endif // __DEALER_XBT_SETTLEMENT_CONTAINER_H__ diff --git a/BlockSettleUILib/Trading/MarketDataModel.cpp b/BlockSettleUILib/Trading/MarketDataModel.cpp deleted file mode 100644 index 6f4477fe4..000000000 --- a/BlockSettleUILib/Trading/MarketDataModel.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "MarketDataModel.h" -#include "CommonTypes.h" -#include "Colors.h" -#include - -#include "UiUtils.h" - -MarketDataModel::MarketDataModel(const QStringList &showSettings, QObject* parent) - : QStandardItemModel(parent) -{ - QStringList headerLabels; - for (int col = static_cast(MarketDataColumns::First); col < static_cast(MarketDataColumns::ColumnsCount); col++) { - headerLabels << columnName(static_cast(col)); - } - setHorizontalHeaderLabels(headerLabels); - - for (int col = static_cast(MarketDataColumns::First) + 1; col < static_cast(MarketDataColumns::ColumnsCount); col++) { - horizontalHeaderItem(col)->setTextAlignment(Qt::AlignCenter); - } - - for (const auto &setting : showSettings) { - instrVisible_.insert(setting); - } - - timer_.setInterval(500); - connect(&timer_, &QTimer::timeout, this, &MarketDataModel::ticker); - timer_.start(); -} - -QString MarketDataModel::columnName(MarketDataColumns col) const -{ - switch (col) - { - case MarketDataColumns::Product: return tr("Security"); - case MarketDataColumns::BidPrice: return tr("Bid"); - case MarketDataColumns::OfferPrice: return tr("Ask"); - case MarketDataColumns::LastPrice: return tr("Last"); - case MarketDataColumns::DailyVol: return tr("24h Volume"); - case MarketDataColumns::EmptyColumn: return QString(); - default: return tr("Unknown"); - } -} - -static double toDoubleFromPriceStr(const QString &priceStr) -{ - if (priceStr.isEmpty()) { - return std::numeric_limits::infinity(); - } - bool ok; - double rv = priceStr.toDouble(&ok); - if (!ok) { - rv = QLocale().toDouble(priceStr, &ok); - } - if (!ok) { - return std::numeric_limits::infinity(); - } - return rv; -} - -QToggleItem* priceItem(const QString &price) -{ - auto item = new QToggleItem(price); - item->setData(Qt::AlignRight, Qt::TextAlignmentRole); - return item; -} - -QToggleItem *MarketDataModel::getGroup(bs::network::Asset::Type assetType) -{ - QString productGroup; - if (assetType == bs::network::Asset::Undefined) { - productGroup = tr("Rejected"); - } - else { - productGroup = tr(bs::network::Asset::toString(assetType)); - } - auto groupItems = findItems(productGroup); - QToggleItem* groupItem = nullptr; - if (groupItems.isEmpty()) { - groupItem = new QToggleItem(productGroup, isVisible(productGroup)); - groupItem->setData(-1); - appendRow(QList() << groupItem); - } - else { - groupItem = static_cast(groupItems.first()); - } - return groupItem; -} - -struct PriceItem { - QString str; - double value; -}; -using PriceMap = std::map; - -static QString getVolumeString(double value, bs::network::Asset::Type at) -{ - if (qFuzzyIsNull(value)) { - return QString{}; - } - - switch(at) { - case bs::network::Asset::SpotFX: - return UiUtils::displayCurrencyAmount(value); - case bs::network::Asset::SpotXBT: - return UiUtils::displayAmount(value); - case bs::network::Asset::PrivateMarket: - return UiUtils::displayCCAmount(value); - } - - return QString(); -} - -static void FieldsToMap(bs::network::Asset::Type at, const bs::network::MDFields &fields, PriceMap &map) -{ - for (const auto &field : fields) { - switch (field.type) { - case bs::network::MDField::PriceBid: - map[MarketDataModel::MarketDataColumns::BidPrice] = { UiUtils::displayPriceForAssetType(field.value, at), UiUtils::truncatePriceForAsset(field.value, at) }; - break; - case bs::network::MDField::PriceOffer: - map[MarketDataModel::MarketDataColumns::OfferPrice] = { UiUtils::displayPriceForAssetType(field.value, at), UiUtils::truncatePriceForAsset(field.value, at) }; - break; - case bs::network::MDField::PriceLast: - map[MarketDataModel::MarketDataColumns::LastPrice] = { UiUtils::displayPriceForAssetType(field.value, at), UiUtils::truncatePriceForAsset(field.value, at) }; - break; - case bs::network::MDField::DailyVolume: - map[MarketDataModel::MarketDataColumns::DailyVol] = { getVolumeString(field.value, at), field.value }; - break; - case bs::network::MDField::Reject: - map[MarketDataModel::MarketDataColumns::ColumnsCount] = { field.desc, 0 }; - break; - default: break; - } - } -} - -bool MarketDataModel::isVisible(const QString &id) const -{ - if (instrVisible_.empty()) { - return true; - } - const auto itVisible = instrVisible_.find(id); - if (itVisible != instrVisible_.end()) { - return true; - } - return false; -} - -void MarketDataModel::onMDUpdated(bs::network::Asset::Type assetType, const QString &security, bs::network::MDFields mdFields) -{ - if ((assetType == bs::network::Asset::Undefined) && security.isEmpty()) { // Celer disconnected - priceUpdates_.clear(); - removeRows(0, rowCount()); - return; - } - - PriceMap fieldsMap; - FieldsToMap(assetType, mdFields, fieldsMap); - auto groupItem = getGroup(assetType); - auto childRow = groupItem->findRowWithText(security); - const auto timeNow = QDateTime::currentDateTime(); - if (!childRow.empty()) { - for (const auto &price : fieldsMap) { - childRow[static_cast(price.first)]->setText(price.second.str); - childRow[static_cast(price.first)]->setBackground(bgColorForCol(security, price.first, price.second.value, timeNow, childRow)); - } - return; - } - - // If we reach here, the product wasn't found, so we make a new row for it - QToggleItem::QToggleRow items; - if (assetType == bs::network::Asset::Type::Undefined) { - const auto rejItem = new QToggleItem(fieldsMap[MarketDataColumns::ColumnsCount].str); - rejItem->setForeground(Qt::red); - items << rejItem; - } - else { - items << new QToggleItem(security, isVisible(security) | groupItem->isVisible()); - for (int col = static_cast(MarketDataColumns::First) + 1; col < static_cast(MarketDataColumns::ColumnsCount); col++) { - const auto price = fieldsMap[static_cast(col)]; - auto item = priceItem(price.str); - items << item; - } - } - groupItem->addRow(items); -} - -QBrush MarketDataModel::bgColorForCol(const QString &security, MarketDataModel::MarketDataColumns col, double price - , const QDateTime &updTime, const QList &row) -{ - switch (col) { - case MarketDataModel::MarketDataColumns::BidPrice: - case MarketDataModel::MarketDataColumns::OfferPrice: - case MarketDataModel::MarketDataColumns::LastPrice: - { - const auto prev = priceUpdates_[security][col].price; - priceUpdates_[security][col] = { price, updTime, {} }; - if (!qFuzzyIsNull(prev)) { - if (price > prev) { - priceUpdates_[security][col].row = row; - return c_greenColor; - } - else if (price < prev) { - priceUpdates_[security][col].row = row; - return c_redColor; - } - } - break; - } - default: break; - } - return {}; -} - -QStringList MarketDataModel::getVisibilitySettings() const -{ - QStringList rv; - for (int i = 0; i < rowCount(); i++) { - const auto toggleGrp = static_cast(item(i)); - if (toggleGrp == nullptr) { - continue; - } - if (toggleGrp->isVisible()) { - rv << toggleGrp->text(); - continue; - } - for (int i = 0; i < toggleGrp->rowCount(); i++) { - const auto child = static_cast(toggleGrp->child(i, 0)); - if (child == nullptr) { - continue; - } - if (child->isVisible()) { - rv << child->text(); - } - } - } - return rv; -} - -void MarketDataModel::onVisibilityToggled(bool filtered) -{ - for (int i = 0; i < rowCount(); i++) { - auto toggleGrp = static_cast(item(i)); - if (toggleGrp == nullptr) { - continue; - } - toggleGrp->showCheckBox(!filtered); - } - emit needResize(); -} - -void MarketDataModel::ticker() -{ - const auto timeNow = QDateTime::currentDateTime(); - for (const auto &priceUpd : priceUpdates_) { - for (auto price : priceUpd.second) { - if (price.second.row.empty()) { - continue; - } - if (price.second.updated.msecsTo(timeNow) > 3000) { - price.second.row[static_cast(price.first)]->setBackground(QBrush()); - price.second.row.clear(); - } - } - } -} - - -QToggleItem::QToggleRow QToggleItem::findRowWithText(const QString &text, int column) -{ - for (int i = 0; i < rowCount(); i++) { - if (child(i, column)->text() == text) { - QToggleRow rv; - for (int j = 0; j < columnCount(); j++) { - rv << static_cast(child(i, j)); - } - return rv; - } - } - for (const auto invChild : invisibleChildren_) { - if (invChild.at(column)->text() == text) { - return invChild; - } - } - return {}; -} - -void QToggleItem::showCheckBox(bool state, int column) -{ - if (state) { - setCheckState(isVisible_ ? Qt::Checked : Qt::Unchecked); - for (const auto &row : invisibleChildren_) { - appendRow(row); - } - } - - setCheckable(state); - for (int i = 0; i < rowCount(); i++) { - auto tgChild = static_cast(child(i, column)); - tgChild->showCheckBox(state, column); - } - - if (!state) { - setData(QVariant(), Qt::CheckStateRole); - - QList > takenRows; - int i = 0; - while (rowCount() > 0) { - if (i >= rowCount()) { - break; - } - auto tgChild = static_cast(child(i, column)); - if (tgChild->isVisible()) { - i++; - } - else { - takenRows << takeRow(i); - } - } - invisibleChildren_.clear(); - for (const auto row : takenRows) { - QToggleRow tRow; - for (const auto item : row) { - tRow << static_cast(item); - } - invisibleChildren_ << tRow; - } - } -} - -void QToggleItem::setData(const QVariant &value, int role) -{ - QStandardItem::setData(value, role); - - if ((role == Qt::CheckStateRole) && value.isValid()) { - const auto state = static_cast(value.toInt()); - setVisible(state == Qt::Checked); - auto tParent = dynamic_cast(parent()); - if (tParent != nullptr) { - tParent->updateCheckMark(); - } else { - for (int i=0; i < rowCount(); i++) { - auto childItem = dynamic_cast(child(i, 0)); - childItem->setVisible(state == Qt::Checked); - childItem->QStandardItem::setData(state, Qt::CheckStateRole); - } - } - } -} - -void QToggleItem::updateCheckMark(int column) -{ - int nbVisibleChildren = 0; - for (int i = 0; i < rowCount(); i++) { - const auto tgChild = static_cast(child(i, column)); - if (tgChild->isVisible()) { - nbVisibleChildren++; - } - } - if (!nbVisibleChildren) { - QStandardItem::setData(Qt::Unchecked, Qt::CheckStateRole); - } - else { - setVisible(nbVisibleChildren != 0); - QStandardItem::setData((nbVisibleChildren == rowCount()) ? Qt::Checked : Qt::PartiallyChecked, Qt::CheckStateRole); - } -} - -void QToggleItem::addRow(const QToggleItem::QToggleRow &row, int visColumn) -{ - if (row[visColumn]->isVisible()) { - appendRow(row); - } - else { - invisibleChildren_ << row; - } -} - -void QToggleItem::appendRow(const QToggleItem::QToggleRow &row) -{ - QList list; - for (const auto &item : row) { - list << item; - } - QStandardItem::appendRow(list); -} - - -MDSortFilterProxyModel::MDSortFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) -{ } - -bool MDSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - QVariant leftData = sourceModel()->data(left); - QVariant rightData = sourceModel()->data(right); - - static const std::map groups = { - {tr(bs::network::Asset::toString(bs::network::Asset::PrivateMarket)), 0}, - {tr(bs::network::Asset::toString(bs::network::Asset::SpotXBT)), 1}, - {tr(bs::network::Asset::toString(bs::network::Asset::SpotFX)), 2}, - }; - - if (!left.parent().isValid() && !right.parent().isValid()) { - try { - return (groups.at(leftData.toString()) < groups.at(rightData.toString())); - } catch (const std::out_of_range &) { - return true; - } - } - - if ((leftData.type() == QVariant::String) && (rightData.type() == QVariant::String)) { - if ((left.column() > 0) && (right.column() > 0)) { - double priceLeft = toDoubleFromPriceStr(leftData.toString()); - double priceRight = toDoubleFromPriceStr(rightData.toString()); - - if ((priceLeft > 0) && (priceRight > 0)) - return (priceLeft < priceRight); - } - return (leftData.toString() < rightData.toString()); - } - return (leftData < rightData); -} diff --git a/BlockSettleUILib/Trading/MarketDataModel.h b/BlockSettleUILib/Trading/MarketDataModel.h deleted file mode 100644 index a3bfd4867..000000000 --- a/BlockSettleUILib/Trading/MarketDataModel.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __MARKET_DATA_MODEL_H__ -#define __MARKET_DATA_MODEL_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "CommonTypes.h" - - -class QToggleItem : public QStandardItem -{ -public: - QToggleItem(const QString &text, bool visible = true) - : QStandardItem(text), isVisible_(visible) {} - - typedef QList QToggleRow; - QToggleRow findRowWithText(const QString &text, int column = 0); - - void addRow(const QToggleRow &, int visColumn = 0); - void setVisible(bool visible) { isVisible_ = visible; } - bool isVisible() const { return isVisible_; } - void showCheckBox(bool state, int column = 0); - void updateCheckMark(int column = 0); - - void setData(const QVariant &value, int role = Qt::UserRole + 1) override; - -private: - bool isVisible_ = true; - QList invisibleChildren_; - - void appendRow(const QToggleRow &); -}; - -class MarketDataModel : public QStandardItemModel -{ -Q_OBJECT -public: - MarketDataModel(const QStringList &showSettings = {}, QObject *parent = nullptr); - ~MarketDataModel() noexcept = default; - - MarketDataModel(const MarketDataModel&) = delete; - MarketDataModel& operator = (const MarketDataModel&) = delete; - MarketDataModel(MarketDataModel&&) = delete; - MarketDataModel& operator = (MarketDataModel&&) = delete; - - QStringList getVisibilitySettings() const; - -public slots: - void onMDUpdated(bs::network::Asset::Type, const QString &security, bs::network::MDFields); - void onVisibilityToggled(bool filtered); - -signals: - void needResize(); - -private slots: - void ticker(); - -public: - enum class MarketDataColumns : int - { - First, - Product = First, - BidPrice, - OfferPrice, - LastPrice, - DailyVol, - EmptyColumn, - ColumnsCount - }; - -private: - struct PriceUpdate { - double price; - QDateTime updated; - QList row; - }; - typedef std::map PriceByCol; - typedef std::map PriceUpdates; - - std::set instrVisible_; - PriceUpdates priceUpdates_; - QTimer timer_; - -private: - QToggleItem *getGroup(bs::network::Asset::Type); - QString columnName(MarketDataColumns) const; - bool isVisible(const QString &id) const; - QBrush bgColorForCol(const QString &security, MarketDataModel::MarketDataColumns, double price - , const QDateTime &, const QList &row); -}; - - -class MDSortFilterProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT -public: - explicit MDSortFilterProxyModel(QObject *parent = nullptr); - -protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; -}; - -#endif // __MARKET_DATA_MODEL_H__ diff --git a/BlockSettleUILib/Trading/MarketDataWidget.cpp b/BlockSettleUILib/Trading/MarketDataWidget.cpp deleted file mode 100644 index 1a1018578..000000000 --- a/BlockSettleUILib/Trading/MarketDataWidget.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "MarketDataWidget.h" - -#include "ui_MarketDataWidget.h" -#include "MarketDataProvider.h" -#include "MarketDataModel.h" -#include "MDCallbacksQt.h" -#include "TreeViewWithEnterKey.h" - -constexpr int EMPTY_COLUMN_WIDTH = 0; - -bool MarketSelectedInfo::isValid() const -{ - return !productGroup_.isEmpty() && - !currencyPair_.isEmpty() && - !bidPrice_.isEmpty() && - !offerPrice_.isEmpty() - ; -} - -MarketDataWidget::MarketDataWidget(QWidget* parent) - : QWidget(parent) - , ui_(new Ui::MarketDataWidget()) - , marketDataModel_(nullptr) - , mdSortFilterModel_(nullptr) -{ - ui_->setupUi(this); -} - -MarketDataWidget::~MarketDataWidget() -{} - -void MarketDataWidget::init(const std::shared_ptr &appSettings, ApplicationSettings::Setting param - , const std::shared_ptr &mdProvider - , const std::shared_ptr &mdCallbacks) -{ - mdProvider_ = mdProvider; - - QStringList visSettings; - if (appSettings != nullptr) { - settingVisibility_ = param; - visSettings = appSettings->get(settingVisibility_); - appSettings_ = appSettings; - } - marketDataModel_ = new MarketDataModel(visSettings, ui_->treeViewMarketData); - mdSortFilterModel_ = new MDSortFilterProxyModel(ui_->treeViewMarketData); - mdSortFilterModel_->setSourceModel(marketDataModel_); - - ui_->treeViewMarketData->setModel(mdSortFilterModel_); - ui_->treeViewMarketData->setSortingEnabled(true); - - if (appSettings != nullptr) { - mdHeader_ = std::make_shared(Qt::Horizontal, ui_->treeViewMarketData); - connect(mdHeader_.get(), &MDHeader::stateChanged, marketDataModel_, &MarketDataModel::onVisibilityToggled); - connect(mdHeader_.get(), &MDHeader::stateChanged, this, &MarketDataWidget::onHeaderStateChanged); - mdHeader_->setEnabled(false); - mdHeader_->setToolTip(tr("Toggles filtered/selection view")); - mdHeader_->setStretchLastSection(true); - mdHeader_->show(); - } - - ui_->treeViewMarketData->setHeader(mdHeader_.get()); - ui_->treeViewMarketData->header()->setSortIndicator( - static_cast(MarketDataModel::MarketDataColumns::First), Qt::AscendingOrder); - ui_->treeViewMarketData->header()->resizeSection(static_cast(MarketDataModel::MarketDataColumns::EmptyColumn), - EMPTY_COLUMN_WIDTH); - - connect(marketDataModel_, &QAbstractItemModel::rowsInserted, [this]() { - if (mdHeader_ != nullptr) { - mdHeader_->setEnabled(true); - } - }); - connect(mdSortFilterModel_, &QAbstractItemModel::rowsInserted, this, &MarketDataWidget::resizeAndExpand); - connect(marketDataModel_, &MarketDataModel::needResize, this, &MarketDataWidget::resizeAndExpand); - - connect(ui_->treeViewMarketData, &QTreeView::clicked, this, &MarketDataWidget::clicked); - connect(ui_->treeViewMarketData->selectionModel(), &QItemSelectionModel::currentChanged, this, &MarketDataWidget::onSelectionChanged); - - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, marketDataModel_, &MarketDataModel::onMDUpdated); - connect(mdCallbacks.get(), &MDCallbacksQt::MDReqRejected, this, &MarketDataWidget::onMDRejected); - - connect(ui_->pushButtonMDConnection, &QPushButton::clicked, this, &MarketDataWidget::ChangeMDSubscriptionState); - - connect(mdCallbacks.get(), &MDCallbacksQt::WaitingForConnectionDetails, this, &MarketDataWidget::onLoadingNetworkSettings); - connect(mdCallbacks.get(), &MDCallbacksQt::StartConnecting, this, &MarketDataWidget::OnMDConnecting); - connect(mdCallbacks.get(), &MDCallbacksQt::Connected, this, &MarketDataWidget::OnMDConnected); - connect(mdCallbacks.get(), &MDCallbacksQt::Disconnecting, this, &MarketDataWidget::OnMDDisconnecting); - connect(mdCallbacks.get(), &MDCallbacksQt::Disconnected, this, &MarketDataWidget::OnMDDisconnected); - - ui_->pushButtonMDConnection->setText(tr("Subscribe")); -} - -void MarketDataWidget::onLoadingNetworkSettings() -{ - ui_->pushButtonMDConnection->setText(tr("Connecting")); - ui_->pushButtonMDConnection->setEnabled(false); - ui_->pushButtonMDConnection->setToolTip(tr("Waiting for connection details")); -} - -void MarketDataWidget::OnMDConnecting() -{ - ui_->pushButtonMDConnection->setText(tr("Connecting")); - ui_->pushButtonMDConnection->setEnabled(false); - ui_->pushButtonMDConnection->setToolTip(QString{}); -} - -void MarketDataWidget::OnMDConnected() -{ - ui_->pushButtonMDConnection->setText(tr("Disconnect")); - ui_->pushButtonMDConnection->setEnabled(!authorized_); -} - -void MarketDataWidget::OnMDDisconnecting() -{ - ui_->pushButtonMDConnection->setText(tr("Disconnecting")); - ui_->pushButtonMDConnection->setEnabled(false); - mdProvider_->UnsubscribeFromMD(); -} - -void MarketDataWidget::OnMDDisconnected() -{ - ui_->pushButtonMDConnection->setText(tr("Subscribe")); - ui_->pushButtonMDConnection->setEnabled(!authorized_); -} - -void MarketDataWidget::ChangeMDSubscriptionState() -{ - if (mdProvider_->IsConnectionActive()) { - mdProvider_->DisconnectFromMDSource(); - } else { - mdProvider_->SubscribeToMD(); - } -} - -MarketSelectedInfo MarketDataWidget::getRowInfo(const QModelIndex& index) const -{ - if (!index.isValid() || !index.parent().isValid()) { - return {}; - } - - auto pairIndex = mdSortFilterModel_->index(index.row(), static_cast(MarketDataModel::MarketDataColumns::Product), index.parent()); - auto bidIndex = mdSortFilterModel_->index(index.row(), static_cast(MarketDataModel::MarketDataColumns::BidPrice), index.parent()); - auto offerIndex = mdSortFilterModel_->index(index.row(), static_cast(MarketDataModel::MarketDataColumns::OfferPrice), index.parent()); - - MarketSelectedInfo selectedInfo; - selectedInfo.productGroup_ = mdSortFilterModel_->data(index.parent()).toString(); - selectedInfo.currencyPair_ = mdSortFilterModel_->data(pairIndex).toString(); - selectedInfo.bidPrice_ = mdSortFilterModel_->data(bidIndex).toString(); - selectedInfo.offerPrice_ = mdSortFilterModel_->data(offerIndex).toString(); - - return selectedInfo; -} - -TreeViewWithEnterKey* MarketDataWidget::view() const -{ - return ui_->treeViewMarketData; -} - -void MarketDataWidget::setAuthorized(bool authorized) -{ - ui_->pushButtonMDConnection->setEnabled(!authorized); - authorized_ = authorized; -} - -MarketSelectedInfo MarketDataWidget::getCurrentlySelectedInfo() const -{ - if (!ui_->treeViewMarketData) { - return {}; - } - - const QModelIndex index = ui_->treeViewMarketData->selectionModel()->currentIndex(); - return getRowInfo(index); -} - -void MarketDataWidget::onMDRejected(const std::string &security, const std::string &reason) -{ - if (security.empty()) { - return; - } - bs::network::MDFields mdFields = { { bs::network::MDField::Reject, 0, QString::fromStdString(reason) } }; - marketDataModel_->onMDUpdated(bs::network::Asset::Undefined, QString::fromStdString(security), mdFields); -} - -void MarketDataWidget::onRowClicked(const QModelIndex& index) -{ - if (!filteredView_ || !index.isValid()) { - return; - } - - // Tab clicked - if (!index.parent().isValid()) { - emit MDHeaderClicked(); - return; - } - - MarketSelectedInfo selectedInfo = getRowInfo(index); - - switch (static_cast(index.column())) - { - case MarketDataModel::MarketDataColumns::BidPrice: { - emit BidClicked(selectedInfo); - break; - } - case MarketDataModel::MarketDataColumns::OfferPrice: { - emit AskClicked(selectedInfo); - break; - } - default: { - emit CurrencySelected(selectedInfo); - break; - } - } -} - -void MarketDataWidget::onSelectionChanged(const QModelIndex ¤t, const QModelIndex &) -{ - auto sourceIndex = mdSortFilterModel_->index(current.row(), - current.column(), current.parent()); - - onRowClicked(sourceIndex); -} - -void MarketDataWidget::resizeAndExpand() -{ - ui_->treeViewMarketData->expandAll(); - ui_->treeViewMarketData->resizeColumnToContents(0); - ui_->treeViewMarketData->header()->resizeSection(static_cast(MarketDataModel::MarketDataColumns::EmptyColumn), - EMPTY_COLUMN_WIDTH); -} - -void MarketDataWidget::onHeaderStateChanged(bool state) -{ - filteredView_ = state; - marketDataModel_->setHeaderData(0, Qt::Horizontal, state ? tr("Filtered view") : tr("Visibility selection")); - ui_->treeViewMarketData->resizeColumnToContents(0); - ui_->treeViewMarketData->header()->resizeSection(static_cast(MarketDataModel::MarketDataColumns::EmptyColumn), - EMPTY_COLUMN_WIDTH); - - if (state && (appSettings_ != nullptr)) { - const auto settings = marketDataModel_->getVisibilitySettings(); - appSettings_->set(settingVisibility_, settings); - } -} diff --git a/BlockSettleUILib/Trading/MarketDataWidget.h b/BlockSettleUILib/Trading/MarketDataWidget.h deleted file mode 100644 index 6fc760c43..000000000 --- a/BlockSettleUILib/Trading/MarketDataWidget.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __MARKET_DATA_WIDGET_H__ -#define __MARKET_DATA_WIDGET_H__ - -#include -#include -#include -#include "ApplicationSettings.h" - - -namespace Ui { - class MarketDataWidget; -}; - -class ApplicationSettings; -class MarketDataModel; -class MarketDataProvider; -class MDCallbacksQt; -class MDSortFilterProxyModel; -class MDHeader; -class TreeViewWithEnterKey; - -struct MarketSelectedInfo { - QString productGroup_; - QString currencyPair_; - QString bidPrice_; - QString offerPrice_; - - bool isValid() const; -}; - -Q_DECLARE_METATYPE(MarketSelectedInfo); - -class MarketDataWidget : public QWidget -{ -Q_OBJECT - -public: - MarketDataWidget(QWidget* parent = nullptr ); - ~MarketDataWidget() override; - - void init(const std::shared_ptr &appSettings, ApplicationSettings::Setting paramVis - , const std::shared_ptr &, const std::shared_ptr &); - - TreeViewWithEnterKey* view() const; - - void setAuthorized(bool authorized); - MarketSelectedInfo getCurrentlySelectedInfo() const; - -signals: - void CurrencySelected(const MarketSelectedInfo& selectedInfo); - void AskClicked(const MarketSelectedInfo& selectedInfo); - void BidClicked(const MarketSelectedInfo& selectedInfo); - void MDHeaderClicked(); - void clicked(); - -private slots: - void resizeAndExpand(); - void onHeaderStateChanged(bool state); - void onRowClicked(const QModelIndex& index); - void onSelectionChanged(const QModelIndex &, const QModelIndex &); - void onMDRejected(const std::string &security, const std::string &reason); - - void onLoadingNetworkSettings(); - - void OnMDConnecting(); - void OnMDConnected(); - void OnMDDisconnecting(); - void OnMDDisconnected(); - - void ChangeMDSubscriptionState(); - -protected: - MarketSelectedInfo getRowInfo(const QModelIndex& index) const; - -private: - std::unique_ptr ui_; - MarketDataModel * marketDataModel_; - MDSortFilterProxyModel * mdSortFilterModel_; - std::shared_ptr appSettings_; - ApplicationSettings::Setting settingVisibility_; - std::shared_ptr mdHeader_; - bool filteredView_ = true; - std::shared_ptr mdProvider_; - bool authorized_{ false }; -}; - -#include -#include -#include -#include - -class MDHeader : public QHeaderView -{ - Q_OBJECT -public: - MDHeader(Qt::Orientation orient, QWidget *parent = nullptr) : QHeaderView(orient, parent) {} - -protected: - void paintSection(QPainter *painter, const QRect &rect, int logIndex) const override { - painter->save(); - QHeaderView::paintSection(painter, rect, logIndex); - painter->restore(); - - if (logIndex == 0) { - QStyleOptionButton option; - const QSize ch = checkboxSizeHint(); - option.rect = QRect(2, (height() - ch.height()) / 2, ch.width(), ch.height()); - option.state = QStyle::State_Enabled; - option.state |= state_ ? QStyle::State_On : QStyle::State_Off; - - style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option, painter); - } - } - - QSize sectionSizeFromContents(int logicalIndex) const override - { - if (logicalIndex == 0) { - const QSize orig = QHeaderView::sectionSizeFromContents(logicalIndex); - const QSize checkbox = checkboxSizeHint(); - - return QSize(orig.width() + checkbox.width() + 4, - qMax(orig.height(), checkbox.height() + 4)); - } else { - return QHeaderView::sectionSizeFromContents(logicalIndex); - } - } - - void mousePressEvent(QMouseEvent *event) override { - if (QRect(0, 0, checkboxSizeHint().width() + 4, height()).contains(event->x(), event->y())) { - state_ = !state_; - emit stateChanged(state_); - update(); - } - else { - QHeaderView::mousePressEvent(event); - } - } - -private: - QSize checkboxSizeHint() const - { - QStyleOptionButton opt; - return style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt).size(); - } - -private: - bool state_ = true; - -signals: - void stateChanged(bool); -}; - - -#endif // __MARKET_DATA_WIDGET_H__ diff --git a/BlockSettleUILib/Trading/MarketDataWidget.ui b/BlockSettleUILib/Trading/MarketDataWidget.ui deleted file mode 100644 index 92c4f1890..000000000 --- a/BlockSettleUILib/Trading/MarketDataWidget.ui +++ /dev/null @@ -1,159 +0,0 @@ - - - - MarketDataWidget - - - - 0 - 0 - 629 - 240 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - Market Data - - - true - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Subscribe - - - - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - true - - - true - - - - - - - - TreeViewWithEnterKey - QTreeView -
TreeViewWithEnterKey.h
-
-
- - -
diff --git a/BlockSettleUILib/Trading/OtcClient.cpp b/BlockSettleUILib/Trading/OtcClient.cpp deleted file mode 100644 index 5a832f879..000000000 --- a/BlockSettleUILib/Trading/OtcClient.cpp +++ /dev/null @@ -1,1839 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OtcClient.h" - -#include -#include -#include - -#include - -#include "AddressVerificator.h" -#include "AuthAddressManager.h" -#include "BtcUtils.h" -#include "CommonTypes.h" -#include "EncryptionUtils.h" -#include "OfflineSigner.h" -#include "ProtobufUtils.h" -#include "TradesUtils.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncHDLeaf.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "ApplicationSettings.h" - -#include "bs_proxy_terminal_pb.pb.h" -#include "otc.pb.h" - -using namespace Blocksettle::Communication::Otc; -using namespace Blocksettle::Communication; -using namespace bs::network; -using namespace bs::network::otc; -using namespace bs::sync; - -struct OtcClientDeal -{ - bs::network::otc::Side side{}; - - std::string hdWalletId; - std::string settlementId; - bs::Address settlementAddr; - - bs::core::wallet::TXSignRequest payin; - std::string payinState; - bs::core::wallet::TXSignRequest payout; - - bs::signer::RequestId payinReqId{}; - bs::signer::RequestId payoutReqId{}; - - BinaryData unsignedPayinHash; - - BinaryData signedTx; - - bs::Address ourAuthAddress; - BinaryData cpPubKey; - - int64_t amount{}; - int64_t fee{}; - int64_t price{}; - - bool success{false}; - std::string errorMsg; - - std::unique_ptr addressVerificator; - - bs::network::otc::PeerPtr peer; - ValidityHandle peerHandle; - - bs::Address cpAuthAddress() const - { - return bs::Address::fromPubKey(cpPubKey, AddressEntryType_P2WPKH); - } - - bs::Address authAddress(bool forSeller) const - { - const bool weSell = (side == bs::network::otc::Side::Sell); - if (forSeller == weSell) { - return ourAuthAddress; - } else { - return cpAuthAddress(); - } - } - - bool isRequestor() const { return side == bs::network::otc::Side::Sell; } - bs::Address requestorAuthAddress() const { return authAddress(true); } - bs::Address responderAuthAddress() const { return authAddress(false); } - - static OtcClientDeal error(const std::string &msg) - { - OtcClientDeal result; - result.errorMsg = msg; - return result; - } -}; - -namespace { - - const int kContactIdSize = 12; - - const int kSettlementIdHexSize = 64; - const int kTxHashSize = 32; - const int kPubKeySize = 33; - - // Normally pay-in/pay-out timeout is detected using server's status update. - // Use some delay to detect networking problems locally to prevent race. - const auto kLocalTimeoutDelay = std::chrono::seconds(5); - - const auto kStartOtcTimeout = std::chrono::seconds(10); - - bs::sync::PasswordDialogData toPasswordDialogData(const OtcClientDeal &deal - , const bs::core::wallet::TXSignRequest &signRequest - , QDateTime timestamp, bool expandTxInfo) - { - double price = fromCents(deal.price); - - QString qtyProd = UiUtils::XbtCurrency; - QString fxProd = QString::fromStdString("EUR"); - - bs::sync::PasswordDialogData dialogData; - - dialogData.setValue(PasswordDialogData::Market, "XBT"); - - dialogData.setValue(PasswordDialogData::ProductGroup, QObject::tr(bs::network::Asset::toString(bs::network::Asset::SpotXBT))); - dialogData.setValue(PasswordDialogData::Security, "XBT/EUR"); - dialogData.setValue(PasswordDialogData::Product, "XBT"); - dialogData.setValue(PasswordDialogData::FxProduct, fxProd); - - dialogData.setValue(PasswordDialogData::Side, QObject::tr(bs::network::Side::toString(bs::network::Side::Type(deal.side)))); - dialogData.setValue(PasswordDialogData::Price, UiUtils::displayPriceXBT(price)); - - dialogData.setValue(PasswordDialogData::Quantity, qApp->tr("%1 XBT") - .arg(UiUtils::displayAmount(deal.amount))); - - dialogData.setValue(PasswordDialogData::TotalValue, qApp->tr("%1 %2") - .arg(UiUtils::displayAmountForProduct((deal.amount / BTCNumericTypes::BalanceDivider) * price, fxProd, bs::network::Asset::Type::SpotXBT)) - .arg(fxProd)); - - dialogData.setValue(PasswordDialogData::SettlementAddress, deal.settlementAddr.display()); - dialogData.setValue(PasswordDialogData::SettlementId, deal.settlementId); - - dialogData.setValue(PasswordDialogData::IsDealer, !deal.isRequestor()); - dialogData.setValue(PasswordDialogData::RequesterAuthAddress, deal.requestorAuthAddress().display()); - dialogData.setValue(PasswordDialogData::RequesterAuthAddressVerified, deal.isRequestor()); - dialogData.setValue(PasswordDialogData::ResponderAuthAddress, deal.responderAuthAddress().display()); - dialogData.setValue(PasswordDialogData::ResponderAuthAddressVerified, !deal.isRequestor()); - - // Set timestamp that will be used by auth eid server to update timers. - dialogData.setValue(PasswordDialogData::DurationTimestamp, static_cast(timestamp.toSecsSinceEpoch())); - - dialogData.setValue(PasswordDialogData::ExpandTxInfo, expandTxInfo); - - return dialogData; - } - - bs::sync::PasswordDialogData toPasswordDialogDataPayin(const OtcClientDeal &deal - , const bs::core::wallet::TXSignRequest &signRequest - , QDateTime timestamp, bool expandTxInfo) - { - auto dialogData = toPasswordDialogData(deal, signRequest, timestamp, expandTxInfo); - dialogData.setValue(PasswordDialogData::SettlementPayInVisible, true); - dialogData.setValue(PasswordDialogData::Title, QObject::tr("Settlement Pay-In")); - dialogData.setValue(PasswordDialogData::DurationTotal, int(std::chrono::duration_cast(otc::payinTimeout()).count())); - dialogData.setValue(PasswordDialogData::DurationLeft, int(std::chrono::duration_cast(otc::payinTimeout()).count())); - return dialogData; - } - - bs::sync::PasswordDialogData toPasswordDialogDataPayout(const OtcClientDeal &deal - , const bs::core::wallet::TXSignRequest &signRequest - , QDateTime timestamp, bool expandTxInfo) - { - auto dialogData = toPasswordDialogData(deal, signRequest, timestamp, expandTxInfo); - dialogData.setValue(PasswordDialogData::SettlementPayOutVisible, true); - dialogData.setValue(PasswordDialogData::Title, QObject::tr("Settlement Pay-Out")); - dialogData.setValue(PasswordDialogData::DurationTotal, int(std::chrono::duration_cast(otc::payoutTimeout()).count())); - dialogData.setValue(PasswordDialogData::DurationLeft, int(std::chrono::duration_cast(otc::payoutTimeout()).count())); - return dialogData; - } - - bool isValidOffer(const ContactMessage_Offer &offer) - { - return offer.price() > 0 && offer.amount() > 0; - } - - void copyOffer(const Offer &src, ContactMessage_Offer *dst) - { - dst->set_price(src.price); - dst->set_amount(src.amount); - } - - void copyRange(const otc::Range &src, Otc::Range *dst) - { - dst->set_lower(src.lower); - dst->set_upper(src.upper); - } - - void copyRange(const Otc::Range&src, otc::Range *dst) - { - dst->lower = src.lower(); - dst->upper = src.upper(); - } - - PeerPtr findPeer(std::unordered_map> &map, const std::string &contactId) - { - auto it = map.find(contactId); - return it == map.end() ? nullptr : it->second; - } - -} // namespace - -OtcClient::OtcClient(const std::shared_ptr &logger - , const std::shared_ptr &walletsMgr - , const std::shared_ptr &armory - , const std::shared_ptr &signContainer - , const std::shared_ptr &authAddressManager - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr& applicationSettings - , OtcClientParams params - , QObject *parent) - : QObject (parent) - , logger_(logger) - , walletsMgr_(walletsMgr) - , armory_(armory) - , signContainer_(signContainer) - , authAddressManager_(authAddressManager) - , utxoReservationManager_(utxoReservationManager) - , applicationSettings_(applicationSettings) - , params_(std::move(params)) -{ - connect(signContainer.get(), &SignContainer::TXSigned, this, &OtcClient::onTxSigned); -} - -OtcClient::~OtcClient() = default; - -PeerPtr OtcClient::contact(const std::string &contactId) -{ - return findPeer(contactMap_, contactId); -} - -PeerPtr OtcClient::request(const std::string &contactId) -{ - return findPeer(requestMap_, contactId); -} - -PeerPtr OtcClient::response(const std::string &contactId) -{ - return findPeer(responseMap_, contactId); -} - -PeerPtr OtcClient::peer(const std::string &contactId, PeerType type) -{ - if (contactId.size() != kContactIdSize) { - SPDLOG_LOGGER_DEBUG(logger_, "unexpected contact requested: {}", contactId); - } - - switch (type) - { - case bs::network::otc::PeerType::Contact: - return contact(contactId); - case bs::network::otc::PeerType::Request: - return request(contactId); - case bs::network::otc::PeerType::Response: - return response(contactId); - } - - assert(false); - return {}; -} - -void OtcClient::setOwnContactId(const std::string &contactId) -{ - ownContactId_ = contactId; -} - -const std::string &OtcClient::ownContactId() const -{ - return ownContactId_; -} - -bool OtcClient::sendQuoteRequest(const QuoteRequest &request) -{ - if (ownRequest_) { - SPDLOG_LOGGER_ERROR(logger_, "own quote request was already sent"); - return false; - } - - if (ownContactId_.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "own contact id is not set"); - return false; - } - - ownRequest_ = std::make_unique(ownContactId_, PeerType::Request); - ownRequest_->request = request; - ownRequest_->request.timestamp = QDateTime::currentDateTime(); - ownRequest_->isOwnRequest = true; - scheduleCloseAfterTimeout(otc::publicRequestTimeout(), ownRequest_); - - Otc::PublicMessage msg; - auto d = msg.mutable_request(); - d->set_sender_side(Otc::Side(request.ourSide)); - d->set_range(Otc::RangeType(request.rangeType)); - emit sendPublicMessage(BinaryData::fromString(msg.SerializeAsString())); - - updatePublicLists(); - - return true; -} - -bool OtcClient::sendQuoteResponse(const PeerPtr &peer, const QuoteResponse "eResponse) -{ - if (peer->state != State::Idle) { - SPDLOG_LOGGER_ERROR(logger_, "can't send offer to '{}', peer should be in Idle state", peer->toString()); - return false; - } - - if (!isSubRange(otc::getRange(peer->request.rangeType), quoteResponse.amount)) { - SPDLOG_LOGGER_ERROR(logger_, "invalid range"); - return false; - } - - changePeerState(peer, State::QuoteSent); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - peer->response = quoteResponse; - - Otc::ContactMessage msg; - auto d = msg.mutable_quote_response(); - d->set_sender_side(Otc::Side(quoteResponse.ourSide)); - copyRange(quoteResponse.price, d->mutable_price()); - copyRange(quoteResponse.amount, d->mutable_amount()); - send(peer, msg); - - updatePublicLists(); - return true; -} - -bool OtcClient::sendOffer(const PeerPtr &peer, const Offer &offer) -{ - SPDLOG_LOGGER_DEBUG(logger_, "send offer to {} (price: {}, amount: {})", peer->toString(), offer.price, offer.amount); - - if (!verifyOffer(offer)) { - SPDLOG_LOGGER_ERROR(logger_, "invalid offer details"); - return false; - } - - auto settlementLeaf = findSettlementLeaf(offer.authAddress); - if (!settlementLeaf) { - SPDLOG_LOGGER_ERROR(logger_, "can't find settlement leaf with address '{}'", offer.authAddress); - return false; - } - - settlementLeaf->getRootPubkey([this, logger = logger_, peer, offer, handle = peer->validityFlag.handle()] - (const SecureBinaryData &ourPubKey) - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - if (ourPubKey.getSize() != kPubKeySize) { - SPDLOG_LOGGER_ERROR(logger_, "invalid auth address root public key"); - return; - } - - switch (peer->type) { - case PeerType::Contact: - if (peer->state != State::Idle) { - SPDLOG_LOGGER_ERROR(logger_, "can't send offer to '{}', peer should be in idle state", peer->toString()); - return; - } - break; - case PeerType::Request: - SPDLOG_LOGGER_ERROR(logger_, "can't send offer to '{}'", peer->toString()); - return; - case PeerType::Response: - if (peer->state != State::QuoteRecv) { - SPDLOG_LOGGER_ERROR(logger_, "can't send offer to '{}', peer should be in QuoteRecv state", peer->toString()); - return; - } - break; - } - - peer->offer = offer; - peer->ourAuthPubKey = ourPubKey; - peer->isOurSideSentOffer = true; - changePeerState(peer, State::OfferSent); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - - ContactMessage msg; - if (offer.ourSide == otc::Side::Buy) { - auto d = msg.mutable_buyer_offers(); - copyOffer(offer, d->mutable_offer()); - d->set_auth_address_buyer(peer->ourAuthPubKey.toBinStr()); - } else { - auto d = msg.mutable_seller_offers(); - copyOffer(offer, d->mutable_offer()); - } - send(peer, msg); - }); - - return true; -} - -bool OtcClient::pullOrReject(const PeerPtr &peer) -{ - if (peer->isOwnRequest) { - assert(peer == ownRequest_); - - SPDLOG_LOGGER_DEBUG(logger_, "pull own quote request"); - ownRequest_.reset(); - - // This will remove everything when we pull public request. - // We could keep current shield and show that our public request was pulled instead. - responseMap_.clear(); - - Otc::PublicMessage msg; - msg.mutable_close(); - emit sendPublicMessage(BinaryData::fromString(msg.SerializeAsString())); - - updatePublicLists(); - return true; - } - - switch (peer->state) { - case State::QuoteSent: - case State::OfferSent: - case State::OfferRecv: { - SPDLOG_LOGGER_DEBUG(logger_, "pull or reject offer from {}", peer->toString()); - - ContactMessage msg; - msg.mutable_close(); - send(peer, msg); - - switch (peer->type) { - case PeerType::Contact: - resetPeerStateToIdle(peer); - break; - case PeerType::Request: - // Keep public request even if we reject it - resetPeerStateToIdle(peer); - // Need to call this as peer would be removed from "sent requests" list - updatePublicLists(); - break; - case PeerType::Response: - // Remove peer from received responses if we reject it - responseMap_.erase(peer->contactId); - updatePublicLists(); - break; - } - - return true; - } - - case State::WaitBuyerSign: - case State::WaitSellerSeal: { - if (peer->state == State::WaitSellerSeal && peer->offer.ourSide == bs::network::otc::Side::Buy) { - SPDLOG_LOGGER_ERROR(logger_, "buyer can't cancel deal while waiting payin sign", peer->toString()); - return false; - } - - auto deal = deals_.at(peer->settlementId).get(); - ProxyTerminalPb::Request request; - auto d = request.mutable_cancel(); - d->set_settlement_id(deal->settlementId); - emit sendPbMessage(request.SerializeAsString()); - - switch (peer->type) { - case PeerType::Request: - requestMap_.erase(peer->contactId); - updatePublicLists(); - break; - case PeerType::Response: - responseMap_.erase(peer->contactId); - updatePublicLists(); - break; - } - - return true; - } - - default: { - SPDLOG_LOGGER_ERROR(logger_, "can't pull offer from '{}'", peer->toString()); - return false; - } - } -} - -void OtcClient::setReservation(const PeerPtr &peer, bs::UtxoReservationToken&& reserv) -{ - reservedTokens_.erase(peer->contactId); - reservedTokens_.insert({ peer->contactId , std::move(reserv) }); -} - -bs::UtxoReservationToken OtcClient::releaseReservation(const bs::network::otc::PeerPtr &peer) -{ - bs::UtxoReservationToken reservation; - auto token = reservedTokens_.find(peer->contactId); - if (token == reservedTokens_.end()) { - return reservation; - } - - reservation = std::move(token->second); - reservedTokens_.erase(token); - return reservation; -} - -bool OtcClient::acceptOffer(const PeerPtr &peer, const bs::network::otc::Offer &offer) -{ - SPDLOG_LOGGER_DEBUG(logger_, "accept offer from {} (price: {}, amount: {})", peer->toString(), offer.price, offer.amount); - - if (!verifyOffer(offer)) { - SPDLOG_LOGGER_ERROR(logger_, "invalid offer details"); - return false; - } - - auto settlementLeaf = findSettlementLeaf(offer.authAddress); - if (!settlementLeaf) { - SPDLOG_LOGGER_ERROR(logger_, "can't find settlement leaf with address '{}'", offer.authAddress); - return false; - } - - settlementLeaf->getRootPubkey([this, offer, peer, handle = peer->validityFlag.handle(), logger = logger_] - (const SecureBinaryData &ourPubKey) - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - if (peer->state != State::OfferRecv) { - SPDLOG_LOGGER_ERROR(logger_, "can't accept offer from '{}', we should be in OfferRecv state", peer->toString()); - return; - } - - if (ourPubKey.getSize() != kPubKeySize) { - SPDLOG_LOGGER_ERROR(logger_, "invalid auth address root public key"); - return; - } - - assert(offer == peer->offer); - - peer->offer = offer; - peer->ourAuthPubKey = ourPubKey; - - if (peer->offer.ourSide == otc::Side::Sell) { - sendSellerAccepts(peer); - return; - } - - // Need to get other details from seller first. - // They should be available from Accept reply. - ContactMessage msg; - auto d = msg.mutable_buyer_accepts(); - copyOffer(offer, d->mutable_offer()); - d->set_auth_address_buyer(peer->ourAuthPubKey.toBinStr()); - send(peer, msg); - - changePeerState(peer, State::WaitPayinInfo); - }); - - return true; -} - -bool OtcClient::updateOffer(const PeerPtr &peer, const Offer &offer) -{ - SPDLOG_LOGGER_DEBUG(logger_, "update offer from {} (price: {}, amount: {})", peer->toString(), offer.price, offer.amount); - - if (!verifyOffer(offer)) { - SPDLOG_LOGGER_ERROR(logger_, "invalid offer details"); - return false; - } - - auto settlementLeaf = findSettlementLeaf(offer.authAddress); - if (!settlementLeaf) { - SPDLOG_LOGGER_ERROR(logger_, "can't find settlement leaf with address '{}'", offer.authAddress); - return false; - } - - settlementLeaf->getRootPubkey([this, offer, peer, handle = peer->validityFlag.handle(), logger = logger_] - (const SecureBinaryData &ourPubKey) - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - if (peer->state != State::OfferRecv) { - SPDLOG_LOGGER_ERROR(logger_, "can't pull offer from '{}', we should be in OfferRecv state", peer->toString()); - return; - } - - if (ourPubKey.getSize() != kPubKeySize) { - SPDLOG_LOGGER_ERROR(logger_, "invalid auth address root public key"); - return; - } - - // Only price could be updated, amount and side must be the same - assert(offer.price != peer->offer.price); - assert(offer.amount == peer->offer.amount); - assert(offer.ourSide == peer->offer.ourSide); - - peer->offer = offer; - peer->ourAuthPubKey = ourPubKey; - - ContactMessage msg; - if (offer.ourSide == otc::Side::Buy) { - auto d = msg.mutable_buyer_offers(); - copyOffer(offer, d->mutable_offer()); - - d->set_auth_address_buyer(peer->ourAuthPubKey.toBinStr()); - } else { - auto d = msg.mutable_seller_offers(); - copyOffer(offer, d->mutable_offer()); - } - send(peer, msg); - - changePeerState(peer, State::OfferSent); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - }); - - return true; -} - -const PeerPtr &OtcClient::ownRequest() const -{ - return ownRequest_; -} - -void OtcClient::contactConnected(const std::string &contactId) -{ - assert(!contact(contactId)); - contactMap_.emplace(contactId, std::make_shared(contactId, PeerType::Contact)); - emit publicUpdated(); -} - -void OtcClient::contactDisconnected(const std::string &contactId) -{ - const auto &peer = contactMap_.at(contactId); - // Do not try to cancel deal waiting pay-in sign (will result in ban) - if (peer->state == State::WaitBuyerSign) { - pullOrReject(peer); - } - contactMap_.erase(contactId); - reservedTokens_.erase(contactId); - emit publicUpdated(); -} - -void OtcClient::processContactMessage(const std::string &contactId, const BinaryData &data) -{ - ContactMessage message; - bool result = message.ParseFromArray(data.getPtr(), int(data.getSize())); - if (!result) { - SPDLOG_LOGGER_ERROR(logger_, "can't parse OTC message"); - return; - } - - bs::network::otc::PeerPtr peer; - switch (message.contact_type()) { - case Otc::CONTACT_TYPE_PRIVATE: - peer = contact(contactId); - if (!peer) { - SPDLOG_LOGGER_ERROR(logger_, "can't find peer '{}'", contactId); - return; - } - - if (peer->state == State::Blacklisted) { - SPDLOG_LOGGER_DEBUG(logger_, "ignoring message from blacklisted peer '{}'", contactId); - return; - } - break; - - case Otc::CONTACT_TYPE_PUBLIC_REQUEST: - if (!ownRequest_) { - SPDLOG_LOGGER_ERROR(logger_, "response is not expected"); - return; - } - - peer = response(contactId); - if (!peer) { - auto result = responseMap_.emplace(contactId, std::make_shared(contactId, PeerType::Response)); - peer = result.first->second; - emit publicUpdated(); - } - break; - - case Otc::CONTACT_TYPE_PUBLIC_RESPONSE: - peer = request(contactId); - if (!peer) { - SPDLOG_LOGGER_ERROR(logger_, "request is not expected"); - return; - } - break; - - default: - SPDLOG_LOGGER_ERROR(logger_, "unknown message type"); - return; - } - - switch (message.data_case()) { - case ContactMessage::kBuyerOffers: - processBuyerOffers(peer, message.buyer_offers()); - return; - case ContactMessage::kSellerOffers: - processSellerOffers(peer, message.seller_offers()); - return; - case ContactMessage::kBuyerAccepts: - processBuyerAccepts(peer, message.buyer_accepts()); - return; - case ContactMessage::kSellerAccepts: - processSellerAccepts(peer, message.seller_accepts()); - return; - case ContactMessage::kBuyerAcks: - processBuyerAcks(peer, message.buyer_acks()); - return; - case ContactMessage::kClose: - processClose(peer, message.close()); - return; - case ContactMessage::kQuoteResponse: - processQuoteResponse(peer, message.quote_response()); - return; - case ContactMessage::DATA_NOT_SET: - blockPeer("unknown or empty OTC message", peer); - return; - } - - SPDLOG_LOGGER_CRITICAL(logger_, "unknown response was detected!"); -} - -void OtcClient::processPbMessage(const ProxyTerminalPb::Response &response) -{ - switch (response.data_case()) { - case ProxyTerminalPb::Response::kStartOtc: - processPbStartOtc(response.start_otc()); - return; - case ProxyTerminalPb::Response::kUpdateOtcState: - processPbUpdateOtcState(response.update_otc_state()); - return; - case ProxyTerminalPb::Response::DATA_NOT_SET: - SPDLOG_LOGGER_ERROR(logger_, "response from PB is invalid"); - return; - default: - // if not processed - not OTC message. not error - break; - } -} - -void OtcClient::processPublicMessage(QDateTime timestamp, const std::string &contactId, const BinaryData &data) -{ - assert(!ownContactId_.empty()); - if (contactId == ownContactId_) { - return; - } - - Otc::PublicMessage msg; - bool result = msg.ParseFromArray(data.getPtr(), int(data.getSize())); - if (!result) { - SPDLOG_LOGGER_ERROR(logger_, "parsing public OTC message failed"); - return; - } - - switch (msg.data_case()) { - case Otc::PublicMessage::kRequest: - processPublicRequest(timestamp, contactId, msg.request()); - return; - case Otc::PublicMessage::kClose: - processPublicClose(timestamp, contactId, msg.close()); - return; - case Otc::PublicMessage::DATA_NOT_SET: - SPDLOG_LOGGER_ERROR(logger_, "invalid public request detected"); - return; - } - - SPDLOG_LOGGER_CRITICAL(logger_, "unknown public message was detected!"); -} - -void OtcClient::onTxSigned(unsigned reqId, BinaryData signedTX - , bs::error::ErrorCode result, const std::string &errorReason) -{ - auto it = signRequestIds_.find(reqId); - if (it == signRequestIds_.end()) { - return; - } - const auto settlementId = std::move(it->second); - signRequestIds_.erase(it); - - auto dealIt = deals_.find(settlementId); - if (dealIt == deals_.end()) { - SPDLOG_LOGGER_ERROR(logger_, "unknown sign request"); - return; - } - OtcClientDeal *deal = dealIt->second.get(); - - if (result == bs::error::ErrorCode::NoError) { - if (deal->payinReqId == reqId) { - SPDLOG_LOGGER_DEBUG(logger_, "pay-in was succesfully signed, settlementId: {}", deal->settlementId); - deal->signedTx = signedTX; - - ProxyTerminalPb::Request request; - auto d = request.mutable_seal_payin_validity(); - d->set_settlement_id(deal->settlementId); - emit sendPbMessage(request.SerializeAsString()); - } - - if (deal->payoutReqId == reqId) { - SPDLOG_LOGGER_DEBUG(logger_, "pay-out was succesfully signed, settlementId: {}", deal->settlementId); - deal->signedTx = signedTX; - trySendSignedTx(deal); - } - return; - } - else { - SPDLOG_LOGGER_ERROR(logger_, "sign error: {}", errorReason); - } - - if (!deal->peerHandle.isValid()) { - SPDLOG_LOGGER_ERROR(logger_, "peer was destroyed"); - return; - } - auto peer = deal->peer; - peer->activeSettlementId.clear(); - pullOrReject(peer); -} - -void OtcClient::processBuyerOffers(const PeerPtr &peer, const ContactMessage_BuyerOffers &msg) -{ - if (!isValidOffer(msg.offer())) { - blockPeer("invalid offer", peer); - return; - } - - if (msg.auth_address_buyer().size() != kPubKeySize) { - blockPeer("invalid auth_address_buyer in buyer offer", peer); - return; - } - peer->authPubKey = BinaryData::fromString(msg.auth_address_buyer()); - - switch (peer->state) { - case State::Idle: - peer->offer.ourSide = otc::Side::Sell; - peer->offer.amount = msg.offer().amount(); - peer->offer.price = msg.offer().price(); - changePeerState(peer, State::OfferRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - break; - - case State::QuoteSent: - peer->offer.ourSide = otc::Side::Sell; - peer->offer.amount = msg.offer().amount(); - peer->offer.price = msg.offer().price(); - changePeerState(peer, State::OfferRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - break; - - case State::QuoteRecv: - SPDLOG_LOGGER_ERROR(logger_, "not implemented"); - return; - - case State::OfferSent: - if (peer->offer.ourSide != otc::Side::Sell) { - blockPeer("unexpected side in counter-offer", peer); - return; - } - if (peer->offer.amount != msg.offer().amount()) { - blockPeer("invalid amount in counter-offer", peer); - return; - } - - peer->offer.price = msg.offer().price(); - changePeerState(peer, State::OfferRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - break; - - case State::OfferRecv: - case State::WaitPayinInfo: - case State::SentPayinInfo: - blockPeer("unexpected offer", peer); - break; - - case State::Blacklisted: - assert(false); - break; - } -} - -void OtcClient::processSellerOffers(const PeerPtr &peer, const ContactMessage_SellerOffers &msg) -{ - if (!isValidOffer(msg.offer())) { - blockPeer("invalid offer", peer); - return; - } - - switch (peer->state) { - case State::Idle: - peer->offer.ourSide = otc::Side::Buy; - peer->offer.amount = msg.offer().amount(); - peer->offer.price = msg.offer().price(); - changePeerState(peer, State::OfferRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - break; - - case State::QuoteSent: - peer->offer.ourSide = otc::Side::Buy; - peer->offer.amount = msg.offer().amount(); - peer->offer.price = msg.offer().price(); - changePeerState(peer, State::OfferRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - break; - - case State::QuoteRecv: - SPDLOG_LOGGER_ERROR(logger_, "not implemented"); - return; - - case State::OfferSent: - if (peer->offer.ourSide != otc::Side::Buy) { - blockPeer("unexpected side in counter-offer", peer); - return; - } - if (peer->offer.amount != msg.offer().amount()) { - blockPeer("invalid amount in counter-offer", peer); - return; - } - - peer->offer.price = msg.offer().price(); - changePeerState(peer, State::OfferRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - break; - - case State::OfferRecv: - case State::WaitPayinInfo: - case State::SentPayinInfo: - blockPeer("unexpected offer", peer); - break; - - case State::Blacklisted: - assert(false); - break; - } -} - -void OtcClient::processBuyerAccepts(const PeerPtr &peer, const ContactMessage_BuyerAccepts &msg) -{ - if (peer->state != State::OfferSent || peer->offer.ourSide != otc::Side::Sell) { - blockPeer("unexpected BuyerAccepts message, should be in OfferSent state and be seller", peer); - return; - } - - if (msg.offer().price() != peer->offer.price || msg.offer().amount() != peer->offer.amount) { - blockPeer("wrong accepted price or amount in BuyerAccepts message", peer); - return; - } - - if (msg.auth_address_buyer().size() != kPubKeySize) { - blockPeer("invalid auth_address in BuyerAccepts message", peer); - return; - } - peer->authPubKey = BinaryData::fromString(msg.auth_address_buyer()); - - sendSellerAccepts(peer); -} - -void OtcClient::processSellerAccepts(const PeerPtr &peer, const ContactMessage_SellerAccepts &msg) -{ - if (msg.offer().price() != peer->offer.price || msg.offer().amount() != peer->offer.amount) { - blockPeer("wrong accepted price or amount in SellerAccepts message", peer); - return; - } - - if (msg.settlement_id().size() != kSettlementIdHexSize) { - blockPeer("invalid settlement_id in SellerAccepts message", peer); - return; - } - const auto &settlementId = msg.settlement_id(); - - if (msg.auth_address_seller().size() != kPubKeySize) { - blockPeer("invalid auth_address_seller in SellerAccepts message", peer); - return; - } - peer->authPubKey = BinaryData::fromString(msg.auth_address_seller()); - - if (msg.payin_tx_id().size() != kTxHashSize) { - blockPeer("invalid payin_tx_id in SellerAccepts message", peer); - return; - } - peer->payinTxIdFromSeller = BinaryData::fromString(msg.payin_tx_id()); - - createBuyerRequest(settlementId, peer, [this, peer, settlementId, offer = peer->offer - , handle = peer->validityFlag.handle(), logger = logger_] (OtcClientDeal &&deal) - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - if (!deal.success) { - SPDLOG_LOGGER_ERROR(logger_, "creating pay-out sign request fails: {}", deal.errorMsg); - return; - } - - if ((peer->state != State::WaitPayinInfo && peer->state != State::OfferSent) || peer->offer.ourSide != otc::Side::Buy) { - blockPeer("unexpected SellerAccepts message, should be in WaitPayinInfo or OfferSent state and be buyer", peer); - return; - } - - if (offer != peer->offer) { - SPDLOG_LOGGER_ERROR(logger_, "offer details have changed unexpectedly"); - return; - } - - auto unsignedPayout = deal.payout.serializeState(); - - deal.peer = peer; - deal.peerHandle = std::move(handle); - deals_.emplace(settlementId, std::make_unique(std::move(deal))); - - ContactMessage msg; - msg.mutable_buyer_acks()->set_settlement_id(settlementId); - send(peer, msg); - - changePeerState(peer, otc::State::WaitVerification); - - ProxyTerminalPb::Request request; - auto d = request.mutable_verify_otc(); - d->set_is_seller(false); - d->set_price(peer->offer.price); - d->set_amount(peer->offer.amount); - d->set_settlement_id(settlementId); - d->set_auth_address_buyer(peer->ourAuthPubKey.toBinStr()); - d->set_auth_address_seller(peer->authPubKey.toBinStr()); - d->set_unsigned_tx(unsignedPayout.SerializeAsString()); - d->set_payin_tx_hash(peer->payinTxIdFromSeller.toBinStr()); - d->set_chat_id_buyer(ownContactId_); - d->set_chat_id_seller(peer->contactId); - emit sendPbMessage(request.SerializeAsString()); - }); -} - -void OtcClient::processBuyerAcks(const PeerPtr &peer, const ContactMessage_BuyerAcks &msg) -{ - if (peer->state != State::SentPayinInfo || peer->offer.ourSide != otc::Side::Sell) { - blockPeer("unexpected BuyerAcks message, should be in SentPayinInfo state and be seller", peer); - return; - } - - const auto &settlementId = msg.settlement_id(); - - const auto it = deals_.find(settlementId); - if (it == deals_.end()) { - SPDLOG_LOGGER_ERROR(logger_, "unknown settlementId from BuyerAcks: {}", settlementId); - return; - } - const auto &deal = it->second; - assert(deal->success); - - changePeerState(peer, otc::State::WaitVerification); - - ProxyTerminalPb::Request request; - - auto d = request.mutable_verify_otc(); - d->set_is_seller(true); - d->set_price(peer->offer.price); - d->set_amount(peer->offer.amount); - d->set_settlement_id(settlementId); - d->set_auth_address_buyer(peer->authPubKey.toBinStr()); - d->set_auth_address_seller(peer->ourAuthPubKey.toBinStr()); - d->set_unsigned_tx(deal->payinState); - - d->set_payin_tx_hash(deal->unsignedPayinHash.toBinStr()); - - d->set_chat_id_seller(ownContactId_); - d->set_chat_id_buyer(peer->contactId); - emit sendPbMessage(request.SerializeAsString()); -} - -void OtcClient::processClose(const PeerPtr &peer, const ContactMessage_Close &msg) -{ - switch (peer->state) { - case State::QuoteSent: - case State::QuoteRecv: - case State::OfferSent: - case State::OfferRecv: - case State::WaitPayinInfo: { - if (peer->type == PeerType::Response) { - SPDLOG_LOGGER_DEBUG(logger_, "remove active response because peer have sent close message"); - responseMap_.erase(peer->contactId); - updatePublicLists(); - return; - } - - resetPeerStateToIdle(peer); - if (peer->type != PeerType::Contact) { - updatePublicLists(); - } - break; - } - - case State::Idle: - // Could happen if both sides press cancel at the same time - break; - - case State::WaitVerification: - case State::WaitBuyerSign: - case State::WaitSellerSeal: - case State::WaitSellerSign: - // After sending verification details both sides should use PB only - SPDLOG_LOGGER_DEBUG(logger_, "ignoring unexpected close request"); - break; - - case State::SentPayinInfo: { - blockPeer("unexpected close", peer); - break; - } - - case State::Blacklisted: { - assert(false); - break; - } - } -} - -void OtcClient::processQuoteResponse(const PeerPtr &peer, const ContactMessage_QuoteResponse &msg) -{ - if (!ownRequest_) { - SPDLOG_LOGGER_ERROR(logger_, "own request is not available"); - return; - } - - if (peer->type != PeerType::Response) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected request"); - return; - } - - changePeerState(peer, State::QuoteRecv); - scheduleCloseAfterTimeout(otc::negotiationTimeout(), peer); - peer->response.ourSide = otc::switchSide(otc::Side(msg.sender_side())); - copyRange(msg.price(), &peer->response.price); - copyRange(msg.amount(), &peer->response.amount); - - updatePublicLists(); -} - -void OtcClient::processPublicRequest(QDateTime timestamp, const std::string &contactId, const PublicMessage_Request &msg) -{ - auto range = otc::RangeType(msg.range()); - if (range < otc::firstRangeValue(params_.env) || range > otc::lastRangeValue(params_.env)) { - SPDLOG_LOGGER_ERROR(logger_, "invalid range"); - return; - } - - requestMap_.erase(contactId); - auto result = requestMap_.emplace(contactId, std::make_shared(contactId, PeerType::Request)); - const auto &peer = result.first->second; - - peer->request.ourSide = otc::switchSide(otc::Side(msg.sender_side())); - peer->request.rangeType = range; - peer->request.timestamp = timestamp; - - updatePublicLists(); -} - -void OtcClient::processPublicClose(QDateTime timestamp, const std::string &contactId, const PublicMessage_Close &msg) -{ - requestMap_.erase(contactId); - - updatePublicLists(); -} - -void OtcClient::processPbStartOtc(const ProxyTerminalPb::Response_StartOtc &response) -{ - auto it = waitSettlementIds_.find(response.request_id()); - if (it == waitSettlementIds_.end()) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected StartOtc response: can't find request"); - return; - } - auto handle = std::move(it->second.handle); - auto peer = it->second.peer; - waitSettlementIds_.erase(it); - - const auto &settlementId = response.settlement_id(); - - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger_, "peer was destroyed"); - return; - } - - createSellerRequest(settlementId, peer, [this, peer, settlementId, offer = peer->offer - , handle = peer->validityFlag.handle(), logger = logger_](OtcClientDeal &&deal) - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - if (!deal.success) { - SPDLOG_LOGGER_ERROR(logger_, "creating pay-in sign request fails: {}", deal.errorMsg); - return; - } - - if (offer.ourSide != otc::Side::Sell) { - SPDLOG_LOGGER_ERROR(logger_, "can't send pay-in info, wrong side"); - return; - } - - if (offer != peer->offer) { - SPDLOG_LOGGER_ERROR(logger_, "offer details have changed unexpectedly"); - return; - } - - ContactMessage msg; - auto d = msg.mutable_seller_accepts(); - copyOffer(peer->offer, d->mutable_offer()); - d->set_settlement_id(settlementId); - d->set_auth_address_seller(peer->ourAuthPubKey.toBinStr()); - d->set_payin_tx_id(deal.unsignedPayinHash.toBinStr()); - d->set_payin_tx(deal.payin.serializeState().SerializeAsString()); - send(peer, msg); - - deal.peer = peer; - deal.peerHandle = std::move(handle); - deal.payinState = deal.payin.serializeState().SerializeAsString(); - deals_.emplace(settlementId, std::make_unique(std::move(deal))); - - changePeerState(peer, State::SentPayinInfo); - }); -} - -void OtcClient::processPbUpdateOtcState(const ProxyTerminalPb::Response_UpdateOtcState &response) -{ - auto it = deals_.find(response.settlement_id()); - if (it == deals_.end()) { - SPDLOG_LOGGER_ERROR(logger_, "unknown settlementId in UpdateOtcState message"); - return; - } - auto deal = it->second.get(); - - switch (response.state()) { - case ProxyTerminalPb::OTC_STATE_WAIT_SELLER_SIGN: - if (deal->side == otc::Side::Sell) { - trySendSignedTx(deal); - } - break; - default: - break; - } - - if (!deal->peerHandle.isValid()) { - SPDLOG_LOGGER_ERROR(logger_, "peer was destroyed"); - return; - } - auto peer = deal->peer; - - SPDLOG_LOGGER_DEBUG(logger_, "change OTC trade state to: {}, settlementId: {}" - , response.settlement_id(), ProxyTerminalPb::OtcState_Name(response.state())); - - auto timestamp = QDateTime::fromMSecsSinceEpoch(response.timestamp_ms()); - - switch (response.state()) { - case ProxyTerminalPb::OTC_STATE_FAILED: { - switch (peer->state) { - case State::WaitVerification: - case State::WaitBuyerSign: - case State::WaitSellerSeal: - case State::WaitSellerSign: - break; - default: - SPDLOG_LOGGER_ERROR(logger_, "unexpected state update request"); - return; - } - - SPDLOG_LOGGER_ERROR(logger_, "OTC trade failed: {}", response.error_msg()); - emit peerError(peer, PeerErrorType::Rejected, &response.error_msg()); - - resetPeerStateToIdle(peer); - break; - } - - case ProxyTerminalPb::OTC_STATE_WAIT_BUYER_SIGN: { - if (peer->state != State::WaitVerification) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected state update request"); - return; - } - - if (deal->side == otc::Side::Buy) { - assert(deal->payout.isValid()); - - bs::core::wallet::SettlementData settlData; - settlData.settlementId = BinaryData::CreateFromHex(deal->settlementId); - settlData.cpPublicKey = deal->cpPubKey; - settlData.ownKeyFirst = true; - - auto payoutInfo = toPasswordDialogDataPayout(*deal, deal->payout, timestamp, expandTxDialog()); - auto reqId = signContainer_->signSettlementPayoutTXRequest(deal->payout, settlData, payoutInfo); - signRequestIds_[reqId] = deal->settlementId; - deal->payoutReqId = reqId; - verifyAuthAddresses(deal); - peer->activeSettlementId = BinaryData::fromString(deal->settlementId); - } - - changePeerState(peer, State::WaitBuyerSign); - - QTimer::singleShot(payoutTimeout() + kLocalTimeoutDelay, this, [this, peer, handle = peer->validityFlag.handle()] { - if (!handle.isValid() || peer->state != State::WaitBuyerSign) { - return; - } - emit peerError(peer, PeerErrorType::Timeout, nullptr); - resetPeerStateToIdle(peer); - }); - break; - } - - case ProxyTerminalPb::OTC_STATE_WAIT_SELLER_SEAL: { - if (peer->state != State::WaitBuyerSign) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected state update request"); - return; - } - - if (deal->side == otc::Side::Sell) { - assert(deal->payin.isValid()); - - const auto &authLeaf = walletsMgr_->getAuthWallet(); - if (!authLeaf) { - SPDLOG_LOGGER_ERROR(logger_, "can't find auth wallet"); - return; - } - signContainer_->setSettlCP(authLeaf->walletId(), deal->unsignedPayinHash, BinaryData::CreateFromHex(deal->settlementId), deal->cpPubKey); - signContainer_->setSettlAuthAddr(authLeaf->walletId(), BinaryData::CreateFromHex(deal->settlementId), deal->ourAuthAddress); - - auto payinInfo = toPasswordDialogDataPayin(*deal, deal->payin, timestamp, expandTxDialog()); - auto reqId = signContainer_->signSettlementTXRequest(deal->payin, payinInfo); - signRequestIds_[reqId] = deal->settlementId; - deal->payinReqId = reqId; - verifyAuthAddresses(deal); - peer->activeSettlementId = BinaryData::fromString(deal->settlementId); - } - - changePeerState(peer, State::WaitSellerSeal); - - QTimer::singleShot(payinTimeout() + kLocalTimeoutDelay, this, [this, peer, handle = peer->validityFlag.handle()] { - if (!handle.isValid() || peer->state != State::WaitSellerSeal) { - return; - } - emit peerError(peer, PeerErrorType::Timeout, nullptr); - resetPeerStateToIdle(peer); - }); - break; - } - - case ProxyTerminalPb::OTC_STATE_WAIT_SELLER_SIGN: { - if (peer->state != State::WaitSellerSeal) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected state update request"); - return; - } - changePeerState(peer, State::WaitSellerSign); - break; - } - - case ProxyTerminalPb::OTC_STATE_CANCELLED: { - if (peer->state != State::WaitBuyerSign && peer->state != State::WaitSellerSeal) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected state update request"); - return; - } - emit peerError(peer, PeerErrorType::Canceled, nullptr); - switch (peer->type) { - case PeerType::Contact: - resetPeerStateToIdle(peer); - break; - case PeerType::Request: - requestMap_.erase(peer->contactId); - updatePublicLists(); - break; - case PeerType::Response: - responseMap_.erase(peer->contactId); - updatePublicLists(); - break; - } - - break; - } - - case ProxyTerminalPb::OTC_STATE_SUCCEED: { - if (peer->state != State::WaitSellerSign) { - SPDLOG_LOGGER_ERROR(logger_, "unexpected state update request"); - return; - } - - resetPeerStateToIdle(peer); - break; - } - - default: { - SPDLOG_LOGGER_ERROR(logger_, "unexpected new state value: {}", int(response.state())); - break; - } - } -} - -bool OtcClient::verifyOffer(const Offer &offer) const -{ - assert(offer.ourSide != otc::Side::Unknown); - assert(offer.amount > 0); - assert(offer.price > 0); - assert(!offer.hdWalletId.empty()); - assert(bs::Address::fromAddressString(offer.authAddress).isValid()); - - if (!offer.recvAddress.empty()) { - auto offerRecvAddress = bs::Address::fromAddressString(offer.recvAddress); - assert(offerRecvAddress.isValid()); - auto wallet = walletsMgr_->getWalletByAddress(offerRecvAddress); - - if (!wallet || wallet->type() != bs::core::wallet::Type::Bitcoin) { - SPDLOG_LOGGER_CRITICAL(logger_, "invalid receiving address selected for OTC, selected address: {}", offer.recvAddress); - return false; - } - - auto hdWalletRecv = walletsMgr_->getHDRootForLeaf(wallet->walletId()); - if (!hdWalletRecv || hdWalletRecv->walletId() != offer.hdWalletId) { - SPDLOG_LOGGER_CRITICAL(logger_, "invalid receiving address selected for OTC (invalid hd wallet), selected address: {}", offer.recvAddress); - return false; - } - } - - if (!walletsMgr_->getHDWalletById(offer.hdWalletId)) { - SPDLOG_LOGGER_ERROR(logger_, "hd wallet not found: {}", offer.hdWalletId); - return false; - } - if (offer.ourSide == bs::network::otc::Side::Buy && !offer.inputs.empty()) { - SPDLOG_LOGGER_CRITICAL(logger_, "inputs must be empty for sell"); - return false; - } - for (const auto &input : offer.inputs) { - auto address = bs::Address::fromUTXO(input); - auto wallet = walletsMgr_->getWalletByAddress(address); - if (!wallet) { - SPDLOG_LOGGER_CRITICAL(logger_, "wallet not found for UTXO from address: {}", address.display()); - return false; - } - const auto hdWalletId = walletsMgr_->getHDRootForLeaf(wallet->walletId())->walletId(); - if (hdWalletId != offer.hdWalletId) { - SPDLOG_LOGGER_CRITICAL(logger_, "invalid UTXO, hdWalletId: {}, expected hdWalletId: {}" - , hdWalletId, offer.hdWalletId); - return false; - } - } - - // utxoReservationManager_ is not available in unit tests - if (utxoReservationManager_) { - auto minXbtAmount = bs::tradeutils::minXbtAmount(utxoReservationManager_->feeRatePb()); - if (offer.amount < static_cast(minXbtAmount.GetValue())) { - SPDLOG_LOGGER_ERROR(logger_, "amount is too low: {}, min amount: {}", offer.amount, minXbtAmount.GetValue()); - return false; - } - } - - return true; -} - -void OtcClient::blockPeer(const std::string &reason, const PeerPtr &peer) -{ - SPDLOG_LOGGER_ERROR(logger_, "block broken peer '{}': {}", peer->toString(), reason); - changePeerState(peer, State::Blacklisted); - emit peerUpdated(peer); -} - -void OtcClient::send(const PeerPtr &peer, ContactMessage &msg) -{ - assert(!peer->contactId.empty()); - msg.set_contact_type(Otc::ContactType(peer->type)); - emit sendContactMessage(peer->contactId, BinaryData::fromString(msg.SerializeAsString())); -} - -void OtcClient::createSellerRequest(const std::string &settlementId, const PeerPtr &peer, const OtcClientDealCb &cb) -{ - assert(peer->authPubKey.getSize() == kPubKeySize); - assert(settlementId.size() == kSettlementIdHexSize); - assert(!peer->offer.authAddress.empty()); - assert(peer->offer.ourSide == bs::network::otc::Side::Sell); - - auto primaryHdWallet = walletsMgr_->getPrimaryWallet(); - if (!primaryHdWallet) { - cb(OtcClientDeal::error("can't find primary wallet")); - return; - } - - auto targetHdWallet = walletsMgr_->getHDWalletById(peer->offer.hdWalletId); - if (!targetHdWallet) { - cb(OtcClientDeal::error(fmt::format("can't find wallet: {}", peer->offer.hdWalletId))); - return; - } - - auto group = targetHdWallet->getGroup(targetHdWallet->getXBTGroupType()); - std::vector> xbtWallets; - if (!targetHdWallet->canMixLeaves()) { - assert(peer->offer.walletPurpose); - xbtWallets.push_back(group->getLeaf(*peer->offer.walletPurpose)); - } - else { - xbtWallets = group->getAllLeaves(); - } - - if (xbtWallets.empty()) { - cb(OtcClientDeal::error("can't find XBT wallets")); - return; - } - - bs::tradeutils::PayinArgs args; - initTradesArgs(args, peer, settlementId); - args.fixedInputs = peer->offer.inputs; - args.inputXbtWallets = xbtWallets; - - auto payinCb = bs::tradeutils::PayinResultCb([this, cb, peer, settlementId - , targetHdWallet, handle = peer->validityFlag.handle(), logger = logger_] - (bs::tradeutils::PayinResult payin) - { - QMetaObject::invokeMethod(this, [this, cb, targetHdWallet, settlementId, handle, logger, peer, payin = std::move(payin)] - { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - if (!payin.success) { - SPDLOG_LOGGER_ERROR(logger, "creating unsigned payin failed: {}", payin.errorMsg); - cb(OtcClientDeal::error("invalid pay-in transaction")); - return; - } - - peer->settlementId = settlementId; - - auto result = std::make_shared(); - result->settlementId = settlementId; - result->settlementAddr = payin.settlementAddr; - result->ourAuthAddress = bs::Address::fromAddressString(peer->offer.authAddress); - result->cpPubKey = peer->authPubKey; - result->amount = peer->offer.amount; - result->price = peer->offer.price; - result->hdWalletId = targetHdWallet->walletId(); - result->success = true; - result->side = otc::Side::Sell; - result->payin = std::move(payin.signRequest); - result->payin.expiredTimestamp = std::chrono::system_clock::now() + otc::payinTimeout(); - - result->unsignedPayinHash = payin.payinHash; - - result->fee = int64_t(result->payin.fee); - - const auto &cbResolveSpenders = [result, cb, this](bs::error::ErrorCode errCode - , const Codec_SignerState::SignerState &state) - { - if (errCode == bs::error::ErrorCode::NoError) { - result->payin.armorySigner_.deserializeState(state); - } - cb(std::move(*result)); - }; - signContainer_->resolvePublicSpenders(result->payin, cbResolveSpenders); - }); - }); - - bs::tradeutils::createPayin(std::move(args), std::move(payinCb)); -} - -void OtcClient::createBuyerRequest(const std::string &settlementId, const PeerPtr &peer, const OtcClient::OtcClientDealCb &cb) -{ - assert(peer->authPubKey.getSize() == kPubKeySize); - assert(settlementId.size() == kSettlementIdHexSize); - assert(!peer->offer.authAddress.empty()); - assert(peer->offer.ourSide == bs::network::otc::Side::Buy); - assert(peer->payinTxIdFromSeller.getSize() == kTxHashSize); - - auto targetHdWallet = walletsMgr_->getHDWalletById(peer->offer.hdWalletId); - if (!targetHdWallet) { - cb(OtcClientDeal::error(fmt::format("can't find wallet: {}", peer->offer.hdWalletId))); - return; - } - - auto group = targetHdWallet->getGroup(targetHdWallet->getXBTGroupType()); - std::vector> xbtWallets; - if (!targetHdWallet->canMixLeaves()) { - assert(peer->offer.walletPurpose); - xbtWallets.push_back(group->getLeaf(*peer->offer.walletPurpose)); - } - else { - xbtWallets = group->getAllLeaves(); - } - - bs::tradeutils::PayoutArgs args; - initTradesArgs(args, peer, settlementId); - args.payinTxId = peer->payinTxIdFromSeller; - if (!peer->offer.recvAddress.empty()) { - args.recvAddr = bs::Address::fromAddressString(peer->offer.recvAddress); - } - args.outputXbtWallet = xbtWallets.front(); - - auto payoutCb = bs::tradeutils::PayoutResultCb([this, cb, peer, settlementId, targetHdWallet, handle = peer->validityFlag.handle(), logger = logger_] - (bs::tradeutils::PayoutResult payout) - { - QMetaObject::invokeMethod(this, [cb, targetHdWallet, settlementId, handle, peer, logger, payout = std::move(payout)] { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - peer->settlementId = settlementId; - - OtcClientDeal result; - result.settlementId = settlementId; - result.settlementAddr = payout.settlementAddr; - result.ourAuthAddress = bs::Address::fromAddressString(peer->offer.authAddress); - result.cpPubKey = peer->authPubKey; - result.amount = peer->offer.amount; - result.price = peer->offer.price; - result.hdWalletId = targetHdWallet->walletId(); - result.success = true; - result.side = otc::Side::Buy; - result.payout = std::move(payout.signRequest); - result.fee = int64_t(result.payout.fee); - cb(std::move(result)); - }); - }); - - bs::tradeutils::createPayout(std::move(args), std::move(payoutCb)); -}; - -void OtcClient::sendSellerAccepts(const PeerPtr &peer) -{ - int requestId = genLocalUniqueId(); - waitSettlementIds_.emplace(requestId, SettlementIdRequest{peer, peer->validityFlag.handle()}); - - ProxyTerminalPb::Request request; - auto d = request.mutable_start_otc(); - d->set_request_id(requestId); - emit sendPbMessage(request.SerializeAsString()); - - QTimer::singleShot(kStartOtcTimeout, this, [this, requestId, peer, handle = peer->validityFlag.handle()] { - if (!handle.isValid()) { - return; - } - auto it = waitSettlementIds_.find(requestId); - if (it == waitSettlementIds_.end()) { - return; - } - waitSettlementIds_.erase(it); - SPDLOG_LOGGER_ERROR(logger_, "can't get settlementId from PB: timeout"); - emit peerError(peer, PeerErrorType::Timeout, nullptr); - pullOrReject(peer); - }); -} - -std::shared_ptr OtcClient::findSettlementLeaf(const std::string &ourAuthAddress) -{ - return walletsMgr_->getSettlementLeaf(bs::Address::fromAddressString(ourAuthAddress)); -} - -void OtcClient::changePeerStateWithoutUpdate(const PeerPtr &peer, State state) -{ - SPDLOG_LOGGER_DEBUG(logger_, "changing peer '{}' state from {} to {}" - , peer->toString(), toString(peer->state), toString(state)); - peer->state = state; - peer->stateTimestamp = std::chrono::steady_clock::now(); - - switch (state) - { - case bs::network::otc::State::Idle: - case bs::network::otc::State::Blacklisted: - releaseReservation(peer); - break; - default: - break; - } -} - -void OtcClient::changePeerState(const PeerPtr &peer, bs::network::otc::State state) -{ - changePeerStateWithoutUpdate(peer, state); - emit peerUpdated(peer); -} - -void OtcClient::resetPeerStateToIdle(const PeerPtr &peer) -{ - if (!peer->activeSettlementId.empty()) { - signContainer_->CancelSignTx(peer->activeSettlementId); - peer->activeSettlementId.clear(); - } - - changePeerStateWithoutUpdate(peer, State::Idle); - auto request = std::move(peer->request); - if (!peer->settlementId.empty()) { - deals_.erase(peer->settlementId); - } - *peer = Peer(peer->contactId, peer->type); - peer->request = std::move(request); - emit peerUpdated(peer); -} - -void OtcClient::scheduleCloseAfterTimeout(std::chrono::milliseconds timeout, const PeerPtr &peer) -{ - // Use PreciseTimer to prevent time out earlier than expected - QTimer::singleShot(timeout, Qt::PreciseTimer, this, [this, peer, oldState = peer->state, handle = peer->validityFlag.handle(), timeout] { - if (!handle.isValid() || peer->state != oldState) { - return; - } - // Prevent closing from some old state if peer had switched back and forth - auto diff = std::chrono::steady_clock::now() - peer->stateTimestamp; - if (diff >= timeout) { - pullOrReject(peer); - } - }); -} - -void OtcClient::trySendSignedTx(OtcClientDeal *deal) -{ - ProxyTerminalPb::Request request; - auto d = request.mutable_process_tx(); - d->set_signed_tx(deal->signedTx.toBinStr()); - d->set_settlement_id(deal->settlementId); - emit sendPbMessage(request.SerializeAsString()); - - setComments(deal); -} - -void OtcClient::verifyAuthAddresses(OtcClientDeal *deal) -{ - if (!authAddressManager_) { - SPDLOG_LOGGER_DEBUG(logger_, "authAddressManager_ is not set, auth address verification skipped"); - return; - } - - auto verificatorCb = [this, logger = logger_, handle = deal->peerHandle, settlementId = deal->settlementId - , requesterAuthAddr = deal->requestorAuthAddress(), responderAuthAddr = deal->responderAuthAddress()] - (const bs::Address &address, AddressVerificationState state) - { - QMetaObject::invokeMethod(qApp, [this, logger, handle, state, address, settlementId, requesterAuthAddr, responderAuthAddr] { - if (!handle.isValid()) { - SPDLOG_LOGGER_ERROR(logger, "peer was destroyed"); - return; - } - - SPDLOG_LOGGER_DEBUG(logger_, "counterparty's auth address ({}) status: {}", address.display(), to_string(state)); - if (state != AddressVerificationState::Verified) { - return; - } - - bs::sync::PasswordDialogData dialogData; - dialogData.setValue(PasswordDialogData::SettlementId, settlementId); - if (address == requesterAuthAddr) { - dialogData.setValue(PasswordDialogData::RequesterAuthAddressVerified, true); - } else if (address == responderAuthAddr) { - dialogData.setValue(PasswordDialogData::ResponderAuthAddressVerified, true); - } else { - SPDLOG_LOGGER_ERROR(logger, "unexpected auth address"); - return; - } - signContainer_->updateDialogData(dialogData); - }); - }; - - if (authAddressVerificationRequired(deal)) { - deal->addressVerificator = std::make_unique(logger_, armory_, verificatorCb); - deal->addressVerificator->SetBSAddressList(authAddressManager_->GetBSAddresses()); - deal->addressVerificator->addAddress(deal->cpAuthAddress()); - deal->addressVerificator->startAddressVerification(); - } else { - verificatorCb(deal->cpAuthAddress(), AddressVerificationState::Verified); - } -} - -void OtcClient::setComments(OtcClientDeal *deal) -{ - auto hdWallet = walletsMgr_->getHDWalletById(deal->hdWalletId); - auto group = hdWallet ? hdWallet->getGroup(hdWallet->getXBTGroupType()) : nullptr; - auto leaves = group ? group->getAllLeaves() : std::vector>(); - for (const auto & leaf : leaves) { - const double price = bs::network::otc::fromCents(deal->price); - auto comment = fmt::format("{} XBT/EUR @ {} (OTC)" - , bs::network::otc::toString(deal->side), UiUtils::displayPriceXBT(price).toStdString()); - leaf->setTransactionComment(deal->signedTx, comment); - } -} - -void OtcClient::updatePublicLists() -{ - contacts_.clear(); - contacts_.reserve(contactMap_.size()); - for (auto &item : contactMap_) { - contacts_.push_back(item.second); - } - - requests_.clear(); - requests_.reserve(requestMap_.size() + (ownRequest_ ? 1 : 0)); - if (ownRequest_) { - requests_.push_back(ownRequest_); - } - for (auto &item : requestMap_) { - requests_.push_back(item.second); - } - - responses_.clear(); - responses_.reserve(responseMap_.size()); - for (auto &item : responseMap_) { - responses_.push_back(item.second); - } - - emit publicUpdated(); -} - -void OtcClient::initTradesArgs(bs::tradeutils::Args &args, const PeerPtr &peer, const std::string &settlementId) -{ - args.amount = bs::XBTAmount(static_cast(peer->offer.amount)); - args.settlementId = BinaryData::CreateFromHex(settlementId); - args.walletsMgr = walletsMgr_; - args.ourAuthAddress = bs::Address::fromAddressString(peer->offer.authAddress); - args.cpAuthPubKey = peer->authPubKey; - args.armory = armory_; - args.signContainer = signContainer_; - // utxoReservationManager_ is null in unit tests - if (utxoReservationManager_) { - args.feeRatePb_ = utxoReservationManager_->feeRatePb(); - } -} - -bool OtcClient::expandTxDialog() const -{ - return applicationSettings_->get( - ApplicationSettings::DetailedSettlementTxDialogByDefault); -} - -bool OtcClient::authAddressVerificationRequired(OtcClientDeal *deal) const -{ - if (!applicationSettings_) { - return true; - } - const auto tier1XbtLimit = applicationSettings_->get( - ApplicationSettings::SubmittedAddressXbtLimit); - return static_cast(deal->amount) > tier1XbtLimit; -} diff --git a/BlockSettleUILib/Trading/OtcClient.h b/BlockSettleUILib/Trading/OtcClient.h deleted file mode 100644 index 35c7eb418..000000000 --- a/BlockSettleUILib/Trading/OtcClient.h +++ /dev/null @@ -1,239 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef OTC_CLIENT_H -#define OTC_CLIENT_H - -#include -#include -#include -#include - -#include "BSErrorCode.h" -#include "BinaryData.h" -#include "OtcTypes.h" -#include "UtxoReservationToken.h" - -namespace spdlog { - class logger; -} - -namespace Blocksettle { - namespace Communication { - namespace Otc { - class ContactMessage; - class ContactMessage_BuyerOffers; - class ContactMessage_SellerOffers; - class ContactMessage_BuyerAccepts; - class ContactMessage_SellerAccepts; - class ContactMessage_BuyerAcks; - class ContactMessage_Close; - class ContactMessage_QuoteResponse; - class PublicMessage_Request; - class PublicMessage_Close; - class PublicMessage_PrivateMessage; - } - } -} - -namespace Blocksettle { - namespace Communication { - namespace ProxyTerminalPb { - class Response; - class Response_StartOtc; - class Response_UpdateOtcState; - } - } -} - -namespace bs { - namespace tradeutils { - struct Args; - } - class Address; - namespace core { - namespace wallet { - struct TXSignRequest; - } - } - namespace sync { - class Wallet; - class WalletsManager; - namespace hd { - class SettlementLeaf; - } - } - class UTXOReservationManager; -} - -class ApplicationSettings; -class ArmoryConnection; -class AuthAddressManager; -class WalletSignerContainer; -struct OtcClientDeal; - -struct OtcClientParams -{ - bs::network::otc::Env env{}; -}; - -class OtcClient : public QObject -{ - Q_OBJECT - -public: - // authAddressManager could be null. If not set peer's auth address won't be verified (and verification affects only signer UI for now). - OtcClient(const std::shared_ptr &logger - , const std::shared_ptr &walletsMgr - , const std::shared_ptr &armory - , const std::shared_ptr &signContainer - , const std::shared_ptr &authAddressManager - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr& applicationSettings - , OtcClientParams params - , QObject *parent = nullptr); - ~OtcClient() override; - - bs::network::otc::PeerPtr contact(const std::string &contactId); - bs::network::otc::PeerPtr request(const std::string &contactId); - bs::network::otc::PeerPtr response(const std::string &contactId); - - // Calls one of above methods depending on type - bs::network::otc::PeerPtr peer(const std::string &contactId, bs::network::otc::PeerType type); - - void setOwnContactId(const std::string &contactId); - const std::string &ownContactId() const; - - bool sendQuoteRequest(const bs::network::otc::QuoteRequest &request); - bool sendQuoteResponse(const bs::network::otc::PeerPtr &peer, const bs::network::otc::QuoteResponse "eResponse); - bool sendOffer(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer &offer); - bool acceptOffer(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer &offer); - bool updateOffer(const bs::network::otc::PeerPtr &peer, const bs::network::otc::Offer &offer); - bool pullOrReject(const bs::network::otc::PeerPtr &peer); - void setReservation(const bs::network::otc::PeerPtr &peer, bs::UtxoReservationToken&& reserv); - bs::UtxoReservationToken releaseReservation(const bs::network::otc::PeerPtr &peer); - - const bs::network::otc::PeerPtrs &requests() { return requests_; } - const bs::network::otc::PeerPtrs &responses() { return responses_; } - const bs::network::otc::PeerPtr &ownRequest() const; - -public slots: - void contactConnected(const std::string &contactId); - void contactDisconnected(const std::string &contactId); - void processContactMessage(const std::string &contactId, const BinaryData &data); - void processPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response &response); - void processPublicMessage(QDateTime timestamp, const std::string &contactId, const BinaryData &data); - -signals: - void sendContactMessage(const std::string &contactId, const BinaryData &data); - void sendPbMessage(const std::string &data); - void sendPublicMessage(const BinaryData &data); - - void peerUpdated(const bs::network::otc::PeerPtr &peer); - // Used to update UI when there is some problems (for example deal verification failed) - void peerError(const bs::network::otc::PeerPtr &peer, bs::network::otc::PeerErrorType type, const std::string *errorMsg); - - void publicUpdated(); - -private slots: - void onTxSigned(unsigned reqId, BinaryData signedTX, bs::error::ErrorCode result, const std::string &errorReason); - -private: - using OtcClientDealCb = std::function; - - struct SettlementIdRequest - { - bs::network::otc::PeerPtr peer; - ValidityHandle handle; - }; - - void processQuoteResponse(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_QuoteResponse &msg); - void processBuyerOffers(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_BuyerOffers &msg); - void processSellerOffers(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_SellerOffers &msg); - void processBuyerAccepts(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_BuyerAccepts &msg); - void processSellerAccepts(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_SellerAccepts &msg); - void processBuyerAcks(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_BuyerAcks &msg); - void processClose(const bs::network::otc::PeerPtr &peer, const Blocksettle::Communication::Otc::ContactMessage_Close &msg); - - void processPublicRequest(QDateTime timestamp, const std::string &contactId, const Blocksettle::Communication::Otc::PublicMessage_Request &msg); - void processPublicClose(QDateTime timestamp, const std::string &contactId, const Blocksettle::Communication::Otc::PublicMessage_Close &msg); - - void processPbStartOtc(const Blocksettle::Communication::ProxyTerminalPb::Response_StartOtc &response); - void processPbUpdateOtcState(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOtcState &response); - - // Checks that hdWallet, auth address and recv address (is set) are valid - bool verifyOffer(const bs::network::otc::Offer &offer) const; - void blockPeer(const std::string &reason, const bs::network::otc::PeerPtr &peer); - - void send(const bs::network::otc::PeerPtr &peer, Blocksettle::Communication::Otc::ContactMessage &msg); - - void createSellerRequest(const std::string &settlementId, const bs::network::otc::PeerPtr &peer, const OtcClientDealCb &cb); - void createBuyerRequest(const std::string &settlementId, const bs::network::otc::PeerPtr &peer, const OtcClientDealCb &cb); - void sendSellerAccepts(const bs::network::otc::PeerPtr &peer); - - std::shared_ptr findSettlementLeaf(const std::string &ourAuthAddress); - - void changePeerStateWithoutUpdate(const bs::network::otc::PeerPtr &peer, bs::network::otc::State state); - void changePeerState(const bs::network::otc::PeerPtr &peer, bs::network::otc::State state); - void resetPeerStateToIdle(const bs::network::otc::PeerPtr &peer); - void scheduleCloseAfterTimeout(std::chrono::milliseconds timeout, const bs::network::otc::PeerPtr &peer); - - int genLocalUniqueId() { return ++latestUniqueId_; } - void trySendSignedTx(OtcClientDeal *deal); - void verifyAuthAddresses(OtcClientDeal *deal); - void setComments(OtcClientDeal *deal); - - void updatePublicLists(); - - void initTradesArgs(bs::tradeutils::Args &args, const bs::network::otc::PeerPtr &peer, const std::string &settlementId); - - bool expandTxDialog() const; - bool authAddressVerificationRequired(OtcClientDeal *deal) const; - - std::shared_ptr logger_; - - std::shared_ptr walletsMgr_; - std::shared_ptr armory_; - std::shared_ptr signContainer_; - std::shared_ptr authAddressManager_; - std::shared_ptr utxoReservationManager_; - std::shared_ptr applicationSettings_; - - std::string ownContactId_; - - // Maps settlementId to OtcClientDeal - std::map> deals_; - - int latestUniqueId_{}; - std::map waitSettlementIds_; - - // Maps sign requests to settlementId - std::map signRequestIds_; - - // Own public request if exists - bs::network::otc::PeerPtr ownRequest_; - - // Maps contactId to corresponding Peer - std::unordered_map contactMap_; - std::unordered_map requestMap_; - std::unordered_map responseMap_; - - // Cached pointer lists from the above - bs::network::otc::PeerPtrs contacts_; - bs::network::otc::PeerPtrs requests_; - bs::network::otc::PeerPtrs responses_; - - OtcClientParams params_; - - // Utxo reservation - std::unordered_map reservedTokens_; -}; - -#endif diff --git a/BlockSettleUILib/Trading/OtcUtils.cpp b/BlockSettleUILib/Trading/OtcUtils.cpp deleted file mode 100644 index 02191803d..000000000 --- a/BlockSettleUILib/Trading/OtcUtils.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "OtcUtils.h" - -#include "OtcTypes.h" -#include "UiUtils.h" -#include "otc.pb.h" - -using namespace Blocksettle::Communication; -using namespace bs::network::otc; - -namespace { - - const std::string kSerializePrefix = "OTC:"; - - QString formatResponse(const Otc::ContactMessage::QuoteResponse &response) - { - return QStringLiteral("%1-%2 XBT %3-%4 EUR") - .arg(response.amount().lower()) - .arg(response.amount().upper()) - .arg(UiUtils::displayPriceXBT(fromCents(response.price().lower()))) - .arg(UiUtils::displayPriceXBT(fromCents(response.price().upper()))); - } - - QString formatOffer(const Otc::ContactMessage::Offer &offer) - { - return QStringLiteral("%1 XBT - %2 EUR") - .arg(UiUtils::displayAmount(offer.amount())) - .arg(UiUtils::displayPriceXBT(fromCents(offer.price()))); - } - -} // namespace - -std::string OtcUtils::serializeMessage(const BinaryData &data) -{ - return kSerializePrefix + data.toHexStr(); -} - -BinaryData OtcUtils::deserializeMessage(const std::string &data) -{ - size_t pos = data.find(kSerializePrefix); - if (pos != 0) { - return {}; - } - try { - return BinaryData::CreateFromHex(data.substr(kSerializePrefix.size())); - } catch(...) { - return {}; - } -} - -std::string OtcUtils::serializePublicMessage(const BinaryData &data) -{ - return data.toHexStr(); -} - -BinaryData OtcUtils::deserializePublicMessage(const std::string &data) -{ - try { - return BinaryData::CreateFromHex(data); - } catch(...) { - return {}; - } -} - -QString OtcUtils::toReadableString(const QString &text) -{ - auto msgData = OtcUtils::deserializeMessage(text.toStdString()); - if (msgData.empty()) { - return {}; - } - - Otc::ContactMessage msg; - bool result = msg.ParseFromArray(msgData.getPtr(), int(msgData.getSize())); - if (!result) { - return {}; - } - - switch (msg.data_case()) { - case Otc::ContactMessage::kQuoteResponse: - return QObject::tr("OTC RESPONSE - XBT/EUR - %1").arg(formatResponse(msg.quote_response())); - case Otc::ContactMessage::kBuyerOffers: - return QObject::tr("OTC REQUEST - XBT/EUR - BUY - %1").arg(formatOffer(msg.buyer_offers().offer())); - case Otc::ContactMessage::kSellerOffers: - return QObject::tr("OTC REQUEST - XBT/EUR - SELL - %1").arg(formatOffer(msg.seller_offers().offer())); - case Otc::ContactMessage::kBuyerAccepts: - return QObject::tr("OTC ACCEPT - XBT/EUR - BUY - %1").arg(formatOffer(msg.buyer_accepts().offer())); - case Otc::ContactMessage::kSellerAccepts: - return QObject::tr("OTC ACCEPT - XBT/EUR - SELL - %1").arg(formatOffer(msg.seller_accepts().offer())); - case Otc::ContactMessage::kBuyerAcks: - return QObject::tr("OTC ACCEPTED"); - case Otc::ContactMessage::kClose: - return QObject::tr("OTC CANCEL"); - case Otc::ContactMessage::DATA_NOT_SET: - return QObject::tr("OTC INVALID MESSAGE"); - } - - assert(false); - return {}; -} diff --git a/BlockSettleUILib/Trading/OtcUtils.h b/BlockSettleUILib/Trading/OtcUtils.h deleted file mode 100644 index 2b9c2d745..000000000 --- a/BlockSettleUILib/Trading/OtcUtils.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef OTC_UTILS_H -#define OTC_UTILS_H - -#include -#include -#include "BinaryData.h" - -class OtcUtils -{ -public: - static std::string serializeMessage(const BinaryData &data); - static BinaryData deserializeMessage(const std::string &data); - - static std::string serializePublicMessage(const BinaryData &data); - static BinaryData deserializePublicMessage(const std::string &data); - - // Parse incoming message and convert it into readable string (that will be visible in the UI). - // If not OTC return empty string. - static QString toReadableString(const QString &text); - -}; - -#endif diff --git a/BlockSettleUILib/Trading/QuoteRequestsModel.cpp b/BlockSettleUILib/Trading/QuoteRequestsModel.cpp deleted file mode 100644 index ade65320a..000000000 --- a/BlockSettleUILib/Trading/QuoteRequestsModel.cpp +++ /dev/null @@ -1,1370 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "QuoteRequestsModel.h" - -#include "ApplicationSettings.h" -#include "AssetManager.h" -#include "CelerClient.h" -#include "CelerSubmitQuoteNotifSequence.h" -#include "Colors.h" -#include "CommonTypes.h" -#include "CurrencyPair.h" -#include "DealerCCSettlementContainer.h" -#include "QuoteRequestsWidget.h" -#include "SettlementContainer.h" -#include "UiUtils.h" - -#include - - -QuoteRequestsModel::QuoteRequestsModel(const std::shared_ptr &statsCollector - , std::shared_ptr celerClient, std::shared_ptr appSettings - , QObject* parent) - : QAbstractItemModel(parent) - , secStatsCollector_(statsCollector) - , celerClient_(celerClient) - , appSettings_(appSettings) -{ - timer_.setInterval(500); - connect(&timer_, &QTimer::timeout, this, &QuoteRequestsModel::ticker); - timer_.start(); - - connect(&priceUpdateTimer_, &QTimer::timeout, this, &QuoteRequestsModel::onPriceUpdateTimer); - - setPriceUpdateInterval(appSettings_->get(ApplicationSettings::PriceUpdateInterval)); - - connect(celerClient_.get(), &BaseCelerClient::OnConnectionClosed, - this, &QuoteRequestsModel::clearModel); - connect(this, &QuoteRequestsModel::deferredUpdate, - this, &QuoteRequestsModel::onDeferredUpdate, Qt::QueuedConnection); -} - -QuoteRequestsModel::~QuoteRequestsModel() -{ - secStatsCollector_->saveState(); -} - -int QuoteRequestsModel::columnCount(const QModelIndex &) const -{ - return 10; -} - -QVariant QuoteRequestsModel::data(const QModelIndex &index, int role) const -{ - IndexHelper *idx = static_cast(index.internalPointer()); - - switch (idx->type_) { - case DataType::RFQ : { - RFQ *r = static_cast(idx->data_); - - switch (role) { - case Qt::DisplayRole : { - switch(static_cast(index.column())) { - case Column::SecurityID : { - return r->security_; - } - - case Column::Product : { - return r->product_; - } - case Column::Side : { - return r->sideString_; - } - - case Column::Quantity : { - return r->quantityString_; - } - - case Column::Party : { - return r->party_; - } - - case Column::Status : { - return r->status_.status_; - } - - case Column::QuotedPx : { - return r->quotedPriceString_; - } - - case Column::IndicPx : { - return r->indicativePxString_; - } - - case Column::BestPx : { - return r->bestQuotedPxString_; - } - - default: - return QVariant(); - } - } - - case static_cast(Role::Type) : { - return static_cast(DataType::RFQ); - } - - case static_cast(Role::LimitOfRfqs) : { - return static_cast(r->idx_.parent_->data_)->limit_; - } - - case static_cast(Role::QuotedRfqsCount) : { - return -1; - } - - case static_cast(Role::Quoted) : { - return r->quoted_; - } - - case static_cast(Role::ShowProgress) : { - return r->status_.showProgress_; - } - - case static_cast(Role::Timeout) : { - return r->status_.timeout_; - } - - case static_cast(Role::TimeLeft) : { - return r->status_.timeleft_; - } - - case static_cast(Role::BestQPrice) : { - return r->bestQuotedPx_; - } - - case static_cast(Role::QuotedPrice) : { - return r->quotedPrice_; - } - - case static_cast(Role::AllowFiltering) : { - if (index.column() == 0) { - return true; - } else { - return false; - } - } - - case static_cast(Role::ReqId) : { - return QString::fromStdString(r->reqId_); - } - - case Qt::BackgroundRole : { - switch (static_cast(index.column())) { - case Column::QuotedPx : - return r->quotedPriceBrush_; - - case Column::IndicPx : - return r->indicativePxBrush_; - - case Column::Status : - return r->stateBrush_; - - default : - return QVariant(); - } - } - - case Qt::TextColorRole: { - if (secStatsCollector_ && index.column() < static_cast(Column::Status)) { - return secStatsCollector_->getColorFor(r->security_.toStdString()); - } - else { - return QVariant(); - } - } - - case static_cast(Role::Visible) : { - return r->visible_; - } - - case static_cast(Role::SortOrder) : { - return r->status_.timeleft_; - } - - default: - return QVariant(); - } - } - - case DataType::Group : { - Group *g = static_cast(idx->data_); - - switch (role) { - case Qt::FontRole : { - if (index.column() == static_cast(Column::SecurityID)) { - return g->font_; - } else { - return QVariant(); - } - } - - case static_cast(Role::Type) : { - return static_cast(DataType::Group); - } - - case static_cast(Role::HasHiddenChildren) : { - return (static_cast(g->visibleCount_ + g->quotedRfqsCount_) < - g->rfqs_.size()); - } - - case static_cast(Role::LimitOfRfqs) : { - return g->limit_; - } - - case static_cast(Role::QuotedRfqsCount) : { - return g->quotedRfqsCount_; - } - - case Qt::DisplayRole : { - switch (index.column()) { - case static_cast(Column::SecurityID) : - return g->security_; - - default : - return QVariant(); - } - } - - case static_cast(Role::StatText) : { - return (g->limit_ > 0 ? tr("%1 of %2") - .arg(QString::number(g->visibleCount_ + - (showQuoted_ ? g->quotedRfqsCount_ : 0))) - .arg(QString::number(g->rfqs_.size())) : - tr("%1 RFQ").arg(QString::number(g->rfqs_.size()))); - } - - case static_cast(Role::CountOfRfqs) : { - return static_cast(g->rfqs_.size()); - } - - case Qt::TextColorRole : { - switch (index.column()) { - case static_cast(Column::SecurityID) : { - if (secStatsCollector_) { - return secStatsCollector_->getColorFor(g->security_.toStdString()); - } else { - return QVariant(); - } - } - - default : - return QVariant(); - } - } - - case static_cast(Role::Grade) : { - if (secStatsCollector_) { - return secStatsCollector_->getGradeFor(g->security_.toStdString()); - } else { - return QVariant(); - } - } - - case static_cast(Role::AllowFiltering) : { - return true; - } - - case static_cast(Role::SortOrder) : { - return g->security_; - } - - default : - return QVariant(); - } - } - - case DataType::Market : { - Market *m = static_cast(idx->data_); - - switch (role) { - case static_cast(Role::Grade) : { - return 1; - } - - case static_cast(Role::Type) : { - return static_cast(DataType::Market); - } - - case static_cast(Role::LimitOfRfqs) : { - return m->limit_; - } - - case static_cast(Role::QuotedRfqsCount) : { - return -1; - } - - case Qt::DisplayRole : { - switch (static_cast(index.column())) { - case Column::SecurityID : { - return m->security_; - } - - default : - return QVariant(); - } - } - - case Qt::TextColorRole : { - if (index.column() == static_cast(Column::Product)) { - return c_greenColor; - } else if (index.column() == static_cast(Column::Side)) { - return c_redColor; - } else { - return QVariant(); - } - } - - case Qt::TextAlignmentRole : { - if (index.column() == static_cast(Column::Product) - || index.column() == static_cast(Column::Side)) { - return Qt::AlignRight; - } else { - return QVariant(); - } - } - - case static_cast(Role::SortOrder) : { - if (m->security_ == QLatin1String(bs::network::Asset::toString(bs::network::Asset::Type::PrivateMarket))) { - return 3; - } - else if (m->security_ == QLatin1String(bs::network::Asset::toString(bs::network::Asset::Type::SpotXBT))) { - return 2; - } - else if (m->security_ == QLatin1String(bs::network::Asset::toString(bs::network::Asset::Type::SpotFX))) { - return 1; - } - else if (m->security_ == groupNameSettlements_) { - return 0; - } - } - - default : - return QVariant(); - } - } - - default : - return QVariant(); - } -} - -QModelIndex QuoteRequestsModel::index(int r, int column, const QModelIndex &parent) const -{ - std::size_t row = static_cast(r); - - if (parent.isValid()) { - IndexHelper *idx = static_cast(parent.internalPointer()); - - switch (idx->type_) { - case DataType::Market : { - Market *m = static_cast(idx->data_); - - if (row < m->groups_.size()) { - return createIndex(r, column, &m->groups_.at(row)->idx_); - } else if (row < m->groups_.size() + m->settl_.rfqs_.size()){ - return createIndex(r, column, &m->settl_.rfqs_.at(row - m->groups_.size())->idx_); - } else { - return QModelIndex(); - } - } - - case DataType::Group : { - Group *g = static_cast(idx->data_); - - if (row < g->rfqs_.size()) { - return createIndex(r, column, &g->rfqs_.at(row)->idx_); - } else { - return QModelIndex(); - } - } - - default : - return QModelIndex(); - } - } else if (row < data_.size()) { - return createIndex(r, column, &data_.at(row)->idx_); - } else { - return QModelIndex(); - } -} - -int QuoteRequestsModel::findGroup(IndexHelper *idx) const -{ - if (idx->parent_) { - auto *market = static_cast(idx->parent_->data_); - - auto it = std::find_if(market->groups_.cbegin(), market->groups_.cend(), - [&idx](const std::unique_ptr &g) { return (&g->idx_ == idx); }); - - if (it != market->groups_.cend()) { - return static_cast(std::distance(market->groups_.cbegin(), it)); - } else { - return -1; - } - } else if (idx->type_ == DataType::Market) { - return findMarket(idx); - } else { - return -1; - } -} - -QuoteRequestsModel::Group* QuoteRequestsModel::findGroup(Market *market, const QString &security) const -{ - auto it = std::find_if(market->groups_.cbegin(), market->groups_.cend(), - [&] (const std::unique_ptr &g) { return (g->security_ == security); } ); - - if (it != market->groups_.cend()) { - return it->get(); - } else { - return nullptr; - } -} - -int QuoteRequestsModel::findMarket(IndexHelper *idx) const -{ - auto it = std::find_if(data_.cbegin(), data_.cend(), - [&idx](const std::unique_ptr &m) { return (&m->idx_ == idx); }); - - if (it != data_.cend()) { - return static_cast(std::distance(data_.cbegin(), it)); - } else { - return -1; - } -} - -QuoteRequestsModel::Market* QuoteRequestsModel::findMarket(const QString &name) const -{ - for (size_t i = 0; i < data_.size(); ++i) { - if (data_[i]->security_ == name) { - return data_[i].get(); - } - } - - return nullptr; -} - -QModelIndex QuoteRequestsModel::parent(const QModelIndex &index) const -{ - if (index.isValid()) { - IndexHelper *idx = static_cast(index.internalPointer()); - - if (idx->parent_) { - switch (idx->parent_->type_) { - case DataType::Group : { - return createIndex(findGroup(idx->parent_), 0, idx->parent_); - } - - case DataType::Market : { - return createIndex(findMarket(idx->parent_), 0, idx->parent_); - } - - default : - return QModelIndex(); - } - } else { - return QModelIndex(); - } - } else { - return QModelIndex(); - } -} - -int QuoteRequestsModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) { - IndexHelper *idx = static_cast(parent.internalPointer()); - - switch (idx->type_) { - case DataType::Group : { - return static_cast(static_cast(idx->data_)->rfqs_.size()); - } - - case DataType::Market : { - auto *market = static_cast(idx->data_); - return static_cast(market->groups_.size() + market->settl_.rfqs_.size()); - } - - default : - return 0; - } - } else { - return static_cast(data_.size()); - } -} - -QVariant QuoteRequestsModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation != Qt::Horizontal) { - return QVariant(); - } - - if (role != Qt::DisplayRole) { - return QVariant(); - } - - switch (static_cast(section)) { - case Column::SecurityID: return tr("SecurityID"); - case Column::Product: return tr("Product"); - case Column::Side: return tr("Side"); - case Column::Quantity: return tr("Quantity"); - case Column::Party: return tr("Party"); - case Column::Status: return tr("Status"); - case Column::QuotedPx: return tr("Quoted Price"); - case Column::IndicPx: return tr("Indicative price"); - case Column::BestPx: return tr("Best Quoted price"); - default: return QString(); - } -} - -void QuoteRequestsModel::limitRfqs(const QModelIndex &index, int limit) -{ - if (!index.parent().isValid() && index.row() < static_cast(data_.size())) { - IndexHelper *idx = static_cast(index.internalPointer()); - - auto *m = static_cast(idx->data_); - - m->limit_ = limit; - - for (auto it = m->groups_.begin(), last = m->groups_.end(); it != last; ++it) { - (*it)->limit_ = limit; - clearVisibleFlag(it->get()); - showRfqsFromFront(it->get()); - } - - emit invalidateFilterModel(); - } -} - -QModelIndex QuoteRequestsModel::findMarketIndex(const QString &name) const -{ - auto *m = findMarket(name); - - if (m) { - return createIndex(findMarket(&m->idx_), 0, &m->idx_); - } else { - return QModelIndex(); - } -} - -void QuoteRequestsModel::setPriceUpdateInterval(int interval) -{ - priceUpdateInterval_ = interval; - priceUpdateTimer_.stop(); - - onPriceUpdateTimer(); - - if (priceUpdateInterval_ > 0) { - priceUpdateTimer_.start(priceUpdateInterval_); - } -} - -void QuoteRequestsModel::showQuotedRfqs(bool on) -{ - if (showQuoted_ != on) { - showQuoted_ = on; - - for (auto mit = data_.cbegin(), mlast = data_.cend(); mit != mlast; ++mit) { - for (auto it = (*mit)->groups_.cbegin(), last = (*mit)->groups_.cend(); - it != last; ++it) { - const auto idx = createIndex( - static_cast(std::distance((*mit)->groups_.cbegin(), it)), - static_cast(Column::Product), &(*it)->idx_); - emit dataChanged(idx, idx); - } - } - } -} - -void QuoteRequestsModel::SetAssetManager(const std::shared_ptr& assetManager) -{ - assetManager_ = assetManager; -} - -void QuoteRequestsModel::ticker() { - std::unordered_set deletedRows; - const auto timeNow = QDateTime::currentDateTime(); - - for (const auto &id : pendingDeleteIds_) { - forSpecificId(id, [this](Group *g, int idxItem) { - beginRemoveRows(createIndex(findGroup(&g->idx_), 0, &g->idx_), idxItem, idxItem); - if (g->rfqs_[static_cast(idxItem)]->quoted_) { - --g->quotedRfqsCount_; - } - if (g->rfqs_[static_cast(idxItem)]->visible_) { - --g->visibleCount_; - showRfqsFromBack(g); - } - g->rfqs_.erase(g->rfqs_.begin() + idxItem); - endRemoveRows(); - - emit invalidateFilterModel(); - }); - deletedRows.insert(id); - } - pendingDeleteIds_.clear(); - - for (auto qrn : notifications_) { - const auto timeDiff = timeNow.msecsTo(qrn.second.expirationTime.addMSecs(qrn.second.timeSkewMs)); - if ((timeDiff < 0) || (qrn.second.status == bs::network::QuoteReqNotification::Withdrawn)) { - forSpecificId(qrn.second.quoteRequestId, [this](Group *grp, int itemIndex) { - const auto row = findGroup(&grp->idx_); - beginRemoveRows(createIndex(row, 0, &grp->idx_), itemIndex, itemIndex); - if (grp->rfqs_[static_cast(itemIndex)]->quoted_) { - --grp->quotedRfqsCount_; - } - if (grp->rfqs_[static_cast(itemIndex)]->visible_) { - --grp->visibleCount_; - showRfqsFromBack(grp); - } - grp->rfqs_.erase(grp->rfqs_.begin() + itemIndex); - endRemoveRows(); - - if ((grp->rfqs_.size() == 0) && (row >= 0)) { - const auto m = findMarket(grp->idx_.parent_); - beginRemoveRows(createIndex(m, 0, grp->idx_.parent_), row, row); - data_[m]->groups_.erase(data_[m]->groups_.begin() + row); - endRemoveRows(); - } else { - emit invalidateFilterModel(); - } - }); - deletedRows.insert(qrn.second.quoteRequestId); - } - else if ((qrn.second.status == bs::network::QuoteReqNotification::PendingAck) - || (qrn.second.status == bs::network::QuoteReqNotification::Replied)) { - forSpecificId(qrn.second.quoteRequestId, [timeDiff](Group *grp, int itemIndex) { - grp->rfqs_[static_cast(itemIndex)]->status_.timeleft_ = - static_cast(timeDiff); - }); - } - } - - for (auto delRow : deletedRows) { - notifications_.erase(delRow); - } - - for (const auto &settlContainer : settlContainers_) { - forSpecificId(settlContainer.second->id(), - [timeLeft = settlContainer.second->timeLeftMs()](Group *grp, int itemIndex) { - grp->rfqs_[static_cast(itemIndex)]->status_.timeleft_ = - static_cast(timeLeft); - }); - } - - if (!data_.empty()) { - for (size_t i = 0; i < data_.size(); ++i) { - for (size_t j = 0; j < data_[i]->groups_.size(); ++j) { - if (data_[i]->groups_[j]->rfqs_.empty()) { - continue; - } - emit dataChanged(createIndex(0, static_cast(Column::Status), - &data_[i]->groups_[j]->rfqs_.front()->idx_), - createIndex(static_cast(data_[i]->groups_[j]->rfqs_.size() - 1), - static_cast(Column::Status), - &data_[i]->groups_[j]->rfqs_.back()->idx_)); - } - - if (!data_[i]->settl_.rfqs_.empty()) { - const int r = static_cast(data_[i]->groups_.size()); - emit dataChanged(createIndex(r, static_cast(Column::Status), - &data_[i]->settl_.rfqs_.front()->idx_), - createIndex(r + static_cast(data_[i]->settl_.rfqs_.size()) - 1, - static_cast(Column::Status), - &data_[i]->settl_.rfqs_.back()->idx_)); - } - } - } -} - -void QuoteRequestsModel::onQuoteNotifCancelled(const QString &reqId) -{ - int row = -1; - RFQ *rfq = nullptr; - - forSpecificId(reqId.toStdString(), [&](Group *group, int i) { - row = i; - rfq = group->rfqs_[static_cast(i)].get(); - rfq->quotedPriceString_ = tr("pulled"); - }); - - setStatus(reqId.toStdString(), bs::network::QuoteReqNotification::PendingAck); - - if (row >= 0 && rfq) { - static const QVector roles({Qt::DisplayRole}); - const QModelIndex idx = createIndex(row, static_cast(Column::QuotedPx), &rfq->idx_); - emit dataChanged(idx, idx, roles); - } -} - -void QuoteRequestsModel::onAllQuoteNotifCancelled(const QString &reqId) -{ - int row = -1; - RFQ *rfq = nullptr; - - forSpecificId(reqId.toStdString(), [&](Group *group, int i) { - row = i; - rfq = group->rfqs_[static_cast(i)].get(); - rfq->bestQuotedPxString_.clear(); - }); - - if (row >= 0 && rfq) { - static const QVector roles({ Qt::DisplayRole }); - const QModelIndex idx = createIndex(row, static_cast(Column::BestPx), &rfq->idx_); - emit dataChanged(idx, idx, roles); - } -} - -void QuoteRequestsModel::onQuoteReqCancelled(const QString &reqId, bool byUser) -{ - if (!byUser) { - return; - } - - setStatus(reqId.toStdString(), bs::network::QuoteReqNotification::Withdrawn); -} - -void QuoteRequestsModel::onQuoteRejected(const QString &reqId, const QString &reason) -{ - setStatus(reqId.toStdString(), bs::network::QuoteReqNotification::Rejected, reason); -} - -void QuoteRequestsModel::updateBestQuotePrice(const QString &reqId, double price, bool own, - std::vector> *idxs) -{ - int row = -1; - Group *g = nullptr; - - forSpecificId(reqId.toStdString(), [&](Group *grp, int index) { - row = index; - g = grp; - const auto assetType = grp->rfqs_[static_cast(index)]->assetType_; - - grp->rfqs_[static_cast(index)]->bestQuotedPxString_ = - UiUtils::displayPriceForAssetType(price, assetType); - grp->rfqs_[static_cast(index)]->bestQuotedPx_ = price; - grp->rfqs_[static_cast(index)]->quotedPriceBrush_ = colorForQuotedPrice( - grp->rfqs_[static_cast(index)]->quotedPrice_, price, own); - }); - - if (row >= 0 && g) { - static const QVector roles({static_cast(Qt::DisplayRole), - static_cast(Qt::BackgroundRole)}); - const auto idx1 = createIndex(row, static_cast(Column::QuotedPx), - &g->rfqs_[static_cast(row)]->idx_); - const auto idx2 = createIndex(row, static_cast(Column::BestPx), - &g->rfqs_[static_cast(row)]->idx_); - - if (!idxs) { - emit dataChanged(idx1, idx2, roles); - } else { - idxs->push_back(std::make_pair(idx1, idx2)); - } - } -} - -void QuoteRequestsModel::onBestQuotePrice(const QString reqId, double price, bool own) -{ - if (priceUpdateInterval_ < 1) { - updateBestQuotePrice(reqId, price, own); - } else { - bestQuotePrices_[reqId] = {price, own}; - } -} - -void QuoteRequestsModel::onQuoteReqNotifReplied(const bs::network::QuoteNotification &qn) -{ - int row = -1; - Group *g = nullptr; - bool withdrawn = false; - - forSpecificId(qn.quoteRequestId, [&](Group *group, int i) { - if (group->rfqs_[static_cast(i)]->withdrawn_) { - withdrawn = true; - return; - } - - row = i; - g = group; - - const auto assetType = group->rfqs_[static_cast(i)]->assetType_; - const double quotedPrice = (qn.side == bs::network::Side::Buy) ? qn.bidPx : qn.offerPx; - - group->rfqs_[static_cast(i)]->quotedPriceString_ = - UiUtils::displayPriceForAssetType(quotedPrice, assetType); - group->rfqs_[static_cast(i)]->quotedPrice_ = quotedPrice; - group->rfqs_[static_cast(i)]->quotedPriceBrush_ = - colorForQuotedPrice(quotedPrice, group->rfqs_[static_cast(i)]->bestQuotedPx_); - }); - - if (withdrawn) { - return; // nothing to do, rfq was withdrawn - } - - if (row >= 0 && g) { - static const QVector roles({static_cast(Qt::DisplayRole), - static_cast(Qt::BackgroundRole)}); - const QModelIndex idx = createIndex(row, static_cast(Column::QuotedPx), - &g->rfqs_[static_cast(row)]->idx_); - emit dataChanged(idx, idx, roles); - } - - setStatus(qn.quoteRequestId, bs::network::QuoteReqNotification::Replied); -} - -QBrush QuoteRequestsModel::colorForQuotedPrice(double quotedPrice, double bestQPrice, bool own) -{ - if (qFuzzyIsNull(quotedPrice) || qFuzzyIsNull(bestQPrice)) { - return {}; - } - - if (own && qFuzzyCompare(quotedPrice, bestQPrice)) { - return c_greenColor; - } - - return c_redColor; -} - -void QuoteRequestsModel::onQuoteReqNotifReceived(const bs::network::QuoteReqNotification &qrn) -{ - QString marketName = tr(bs::network::Asset::toString(qrn.assetType)); - auto *market = findMarket(marketName); - - if (!market) { - beginInsertRows(QModelIndex(), static_cast(data_.size()), - static_cast(data_.size())); - data_.push_back(std::unique_ptr(new Market(marketName, - appSettings_->get(UiUtils::limitRfqSetting(qrn.assetType))))); - market = data_.back().get(); - endInsertRows(); - } - - QString groupNameSec = QString::fromStdString(qrn.security); - auto *group = findGroup(market, groupNameSec); - - if (!group) { - beginInsertRows(createIndex(findMarket(&market->idx_), 0, &market->idx_), - static_cast(market->groups_.size()), - static_cast(market->groups_.size())); - QFont font; - font.setBold(true); - market->groups_.push_back(std::unique_ptr(new Group(groupNameSec, - market->limit_, font))); - market->groups_.back()->idx_.parent_ = &market->idx_; - group = market->groups_.back().get(); - endInsertRows(); - } - - insertRfq(group, qrn); -} - -void QuoteRequestsModel::insertRfq(Group *group, const bs::network::QuoteReqNotification &qrn) -{ - auto itQRN = notifications_.find(qrn.quoteRequestId); - - if (itQRN == notifications_.end()) { - const auto assetType = assetManager_->GetAssetTypeForSecurity(qrn.security); - const CurrencyPair cp(qrn.security); - const bool isBid = (qrn.side == bs::network::Side::Buy) ^ (cp.NumCurrency() == qrn.product); - const double indicPrice = isBid ? mdPrices_[qrn.security][Role::BidPrice] : - mdPrices_[qrn.security][Role::OfferPrice]; - - beginInsertRows(createIndex(findGroup(&group->idx_), 0, &group->idx_), - static_cast(group->rfqs_.size()), - static_cast(group->rfqs_.size())); - - group->rfqs_.push_back(std::unique_ptr(new RFQ(QString::fromStdString(qrn.security), - QString::fromStdString(qrn.product), - tr(bs::network::Side::toString(qrn.side)), - QString(), - (qrn.assetType == bs::network::Asset::Type::PrivateMarket) ? - UiUtils::displayCCAmount(qrn.quantity) : UiUtils::displayQty(qrn.quantity, qrn.product), - QString(), - (!qFuzzyIsNull(indicPrice) ? UiUtils::displayPriceForAssetType(indicPrice, assetType) - : QString()), - QString(), - { - quoteReqStatusDesc(qrn.status), - ((qrn.status == bs::network::QuoteReqNotification::Status::PendingAck) - || (qrn.status == bs::network::QuoteReqNotification::Status::Replied)), - 30000 - }, - indicPrice, 0.0, 0.0, - qrn.side, - assetType, - qrn.quoteRequestId))); - - group->rfqs_.back()->idx_.parent_ = &group->idx_; - - endInsertRows(); - - notifications_[qrn.quoteRequestId] = qrn; - - if (group->limit_ > 0 && group->limit_ > group->visibleCount_) { - group->rfqs_.back()->visible_ = true; - ++group->visibleCount_; - - emit invalidateFilterModel(); - } - } - else { - setStatus(qrn.quoteRequestId, qrn.status); - } -} - -bool QuoteRequestsModel::StartCCSignOnOrder(const QString& orderId, QDateTime timestamp) -{ - auto it = settlContainers_.find(orderId.toStdString()); - if (it == settlContainers_.end()) { - return false; - } - - auto container = std::dynamic_pointer_cast(it->second); - if (container != nullptr) { - return container->startSigning(timestamp); - } - - return false; -} - -void QuoteRequestsModel::addSettlementContainer(const std::shared_ptr &container) -{ - const auto &id = container->id(); - settlContainers_[id] = container; - - // Use queued connections to not destroy SettlementContainer inside callbacks - connect(container.get(), &bs::SettlementContainer::failed, this - , &QuoteRequestsModel::deleteSettlement, Qt::QueuedConnection); - connect(container.get(), &bs::SettlementContainer::completed, this - , &QuoteRequestsModel::deleteSettlement, Qt::QueuedConnection); - connect(container.get(), &bs::SettlementContainer::timerExpired, this, [this, id] { - deleteSettlement(id); - }, Qt::QueuedConnection); - - auto *market = findMarket(groupNameSettlements_); - - if (!market) { - beginInsertRows(QModelIndex(), static_cast(data_.size()), - static_cast(data_.size())); - data_.push_back(std::unique_ptr(new Market(groupNameSettlements_, -1))); - market = data_.back().get(); - endInsertRows(); - } - - const auto collector = std::make_shared(container); - const auto assetType = assetManager_->GetAssetTypeForSecurity(container->security()); - const auto amountStr = (container->assetType() == bs::network::Asset::Type::PrivateMarket) - ? UiUtils::displayCCAmount(container->quantity()) - : UiUtils::displayQty(container->quantity(), container->product()); - - beginInsertRows(createIndex(findMarket(&market->idx_), 0, &market->idx_), - static_cast(market->groups_.size() + market->settl_.rfqs_.size()), - static_cast(market->groups_.size() + market->settl_.rfqs_.size())); - - market->settl_.rfqs_.push_back(std::unique_ptr(new RFQ( - QString::fromStdString(container->security()), - QString::fromStdString(container->product()), - tr(bs::network::Side::toString(container->side())), - QString(), - amountStr, - QString(), - UiUtils::displayPriceForAssetType(container->price(), assetType), - QString(), - { - QString(), - true - }, - container->price(), 0.0, 0.0, - container->side(), - assetType, - container->id()))); - - market->settl_.rfqs_.back()->idx_.parent_ = &market->idx_; - - connect(container.get(), &bs::SettlementContainer::timerStarted, - [s = market->settl_.rfqs_.back().get(), this, - row = static_cast(market->groups_.size() + market->settl_.rfqs_.size() - 1)](int msDuration) { - s->status_.timeout_ = msDuration; - const QModelIndex idx = createIndex(row, 0, &s->idx_); - static const QVector roles({static_cast(Role::Timeout)}); - emit dataChanged(idx, idx, roles); - } - ); - - endInsertRows(); -} - -void QuoteRequestsModel::clearModel() -{ - beginResetModel(); - data_.clear(); - endResetModel(); -} - -void QuoteRequestsModel::onDeferredUpdate(const QPersistentModelIndex &index) -{ - if (index.isValid()) { - emit dataChanged(index, index); - } -} - -void QuoteRequestsModel::onPriceUpdateTimer() -{ - std::vector> idxs; - - for (auto it = prices_.cbegin(), last = prices_.cend(); it != last; ++it) { - updatePrices(it->first, it->second.first, it->second.second, &idxs); - } - - prices_.clear(); - - for (auto it = bestQuotePrices_.cbegin(), last = bestQuotePrices_.cend(); it != last; ++it) { - updateBestQuotePrice(it->first, it->second.price_, it->second.own_, &idxs); - } - - bestQuotePrices_.clear(); - - if (!idxs.empty()) { - struct Index { - int row_; - int column_; - }; - - std::map> mapOfIdxs; - - for (const auto &idx: idxs) { - auto it = mapOfIdxs.find(idx.first.parent()); - - if (it != mapOfIdxs.end()) { - if (idx.first.row() < it->second.first.row_) { - it->second.first.row_ = idx.first.row(); - } - - if (idx.first.column() < it->second.first.column_) { - it->second.first.column_ = idx.first.column(); - } - - if (idx.second.row() > it->second.second.row_) { - it->second.second.row_ = idx.second.row(); - } - - if (idx.second.column() > it->second.second.column_) { - it->second.second.column_ = idx.second.column(); - } - } else { - mapOfIdxs[idx.first.parent()] = std::make_pair( - {idx.first.row(), idx.first.column()}, - {idx.second.row(), idx.second.column()}); - } - } - - for (auto it = mapOfIdxs.cbegin(), last = mapOfIdxs.cend(); it != last; ++it) { - emit dataChanged(index(it->second.first.row_, it->second.first.column_, it->first), - index(it->second.second.row_, it->second.second.column_, it->first)); - } - } -} - -void QuoteRequestsModel::deleteSettlement(const std::string &id) -{ - updateSettlementCounters(); - - auto it = settlContainers_.find(id); - if (it != settlContainers_.end()) { - pendingDeleteIds_.insert(id); - it->second->deactivate(); - settlContainers_.erase(it); - } -} - -void QuoteRequestsModel::updateSettlementCounters() -{ - auto * market = findMarket(groupNameSettlements_); - - if (market) { - const int row = findMarket(&market->idx_); - - emit dataChanged(createIndex(row, static_cast(Column::Product), &market->idx_), - createIndex(row, static_cast(Column::Side), &market->idx_)); - } -} - -void QuoteRequestsModel::forSpecificId(const std::string &reqId, const cbItem &cb) -{ - for (size_t i = 0; i < data_.size(); ++i) { - if (!data_[i]->settl_.rfqs_.empty()) { - for (size_t k = 0; k < data_[i]->settl_.rfqs_.size(); ++k) { - if (data_[i]->settl_.rfqs_[k]->reqId_ == reqId) { - cb(&data_[i]->settl_, static_cast(k)); - return; - } - } - } - - for (size_t j = 0; j < data_[i]->groups_.size(); ++j) { - for (size_t k = 0; k < data_[i]->groups_[j]->rfqs_.size(); ++k) { - if (data_[i]->groups_[j]->rfqs_[k]->reqId_ == reqId) { - cb(data_[i]->groups_[j].get(), static_cast(k)); - return; - } - } - } - } -} - -void QuoteRequestsModel::forEachSecurity(const QString &security, const cbItem &cb) -{ - for (size_t i = 0; i < data_.size(); ++i) { - for (size_t j = 0; j < data_[i]->groups_.size(); ++j) { - if (data_[i]->groups_[j]->security_ != security) - continue; - for (size_t k = 0; k < data_[i]->groups_[j]->rfqs_.size(); ++k) { - cb(data_[i]->groups_[j].get(), static_cast(k)); - } - } - } -} - -const bs::network::QuoteReqNotification &QuoteRequestsModel::getQuoteReqNotification(const std::string &id) const -{ - static bs::network::QuoteReqNotification emptyQRN; - - auto itQRN = notifications_.find(id); - if (itQRN == notifications_.end()) return emptyQRN; - return itQRN->second; -} - -double QuoteRequestsModel::getPrice(const std::string &security, Role role) const -{ - const auto itMDP = mdPrices_.find(security); - if (itMDP != mdPrices_.end()) { - const auto itP = itMDP->second.find(role); - if (itP != itMDP->second.end()) { - return itP->second; - } - } - return 0; -} - -QString QuoteRequestsModel::getMarketSecurity(const QModelIndex &index) -{ - for (auto parent = index; parent.isValid(); parent = parent.parent()) { - IndexHelper *idx = static_cast(parent.internalPointer()); - - if (idx->type_ != DataType::Market) { - continue; - } - - Market *marketInfo = static_cast(idx->data_); - if (marketInfo) { - return marketInfo->security_; - } - } - - return {}; -} - -QString QuoteRequestsModel::quoteReqStatusDesc(bs::network::QuoteReqNotification::Status status) -{ - switch (status) { - case bs::network::QuoteReqNotification::Withdrawn: return tr("Withdrawn"); - case bs::network::QuoteReqNotification::PendingAck: return tr("PendingAck"); - case bs::network::QuoteReqNotification::Replied: return tr("Replied"); - case bs::network::QuoteReqNotification::TimedOut: return tr("TimedOut"); - case bs::network::QuoteReqNotification::Rejected: return tr("Rejected"); - default: return QString(); - } -} - -QBrush QuoteRequestsModel::bgColorForStatus(bs::network::QuoteReqNotification::Status status) -{ - switch (status) { - case bs::network::QuoteReqNotification::Withdrawn: return Qt::magenta; - case bs::network::QuoteReqNotification::Rejected: return c_redColor; - case bs::network::QuoteReqNotification::Replied: return c_greenColor; - case bs::network::QuoteReqNotification::TimedOut: return c_yellowColor; - case bs::network::QuoteReqNotification::PendingAck: - default: return QBrush(); - } -} - -void QuoteRequestsModel::setStatus(const std::string &reqId, bs::network::QuoteReqNotification::Status status - , const QString &details) -{ - auto itQRN = notifications_.find(reqId); - if (itQRN != notifications_.end()) { - itQRN->second.status = status; - - forSpecificId(reqId, [this, status, details](Group *grp, int index) { - - auto *rfq = grp->rfqs_[static_cast(index)].get(); - - if (!details.isEmpty()) { - rfq->status_.status_ = details; - } - else { - rfq->status_.status_ = - quoteReqStatusDesc(status); - } - - bool emitUpdate = false; - - if (status == bs::network::QuoteReqNotification::Replied) { - assert(!rfq->withdrawn_); - - if (!rfq->quoted_) { - rfq->quoted_ = true; - ++grp->quotedRfqsCount_; - emitUpdate = true; - } - - if (rfq->visible_) { - rfq->visible_ = false; - --grp->visibleCount_; - showRfqsFromBack(grp); - emitUpdate = true; - } - - emit invalidateFilterModel(); - } - - if (status == bs::network::QuoteReqNotification::Withdrawn) { - if (rfq->quoted_) { - rfq->quoted_ = false; - --grp->quotedRfqsCount_; - emit invalidateFilterModel(); - emitUpdate = true; - } - rfq->withdrawn_ = true; - } - - rfq->stateBrush_ = bgColorForStatus(status); - - const bool showProgress = ((status == bs::network::QuoteReqNotification::Status::PendingAck) - || (status == bs::network::QuoteReqNotification::Status::Replied)); - grp->rfqs_[index]->status_.showProgress_ = showProgress; - - const QModelIndex idx = createIndex(index, static_cast(Column::Status), - &grp->rfqs_[index]->idx_); - emit dataChanged(idx, idx); - - if (emitUpdate) { - const QModelIndex gidx = createIndex(findGroup(&grp->idx_), 0, &grp->idx_); - emit dataChanged(gidx, gidx); - } - }); - - emit quoteReqNotifStatusChanged(itQRN->second); - } -} - -void QuoteRequestsModel::updatePrices(const QString &security, const bs::network::MDField &pxBid, - const bs::network::MDField &pxOffer, std::vector> *idxs) -{ - forEachSecurity(security, [security, pxBid, pxOffer, this, idxs](Group *grp, int index) { - const CurrencyPair cp(security.toStdString()); - const bool isBuy = (grp->rfqs_[static_cast(index)]->side_ == bs::network::Side::Buy) - ^ (cp.NumCurrency() == grp->rfqs_[static_cast(index)]->product_.toStdString()); - double indicPrice = 0; - - if (isBuy && (pxBid.type != bs::network::MDField::Unknown)) { - indicPrice = pxBid.value; - } else if (!isBuy && (pxOffer.type != bs::network::MDField::Unknown)) { - indicPrice = pxOffer.value; - } - - if (indicPrice > 0) { - const auto prevPrice = grp->rfqs_[static_cast(index)]->indicativePx_; - const auto assetType = grp->rfqs_[static_cast(index)]->assetType_; - grp->rfqs_[static_cast(index)]->indicativePxString_ = - UiUtils::displayPriceForAssetType(indicPrice, assetType); - grp->rfqs_[static_cast(index)]->indicativePx_ = indicPrice; - - if (!qFuzzyIsNull(prevPrice)) { - if (indicPrice > prevPrice) { - grp->rfqs_[static_cast(index)]->indicativePxBrush_ = c_greenColor; - } - else if (indicPrice < prevPrice) { - grp->rfqs_[static_cast(index)]->indicativePxBrush_ = c_redColor; - } - } - - const QModelIndex idx = createIndex(index, static_cast(Column::IndicPx), - &grp->rfqs_[static_cast(index)]->idx_); - - if (!idxs) { - emit dataChanged(idx, idx); - } else { - idxs->push_back(std::make_pair(idx, idx)); - } - } - }); -} - -void QuoteRequestsModel::showRfqsFromBack(Group *g) -{ - if (g->limit_ > 0) { - for (auto it = g->rfqs_.rbegin(), last = g->rfqs_.rend(); it != last; ++it) { - if (g->visibleCount_ < g->limit_) { - if (!(*it)->quoted_ && !(*it)->visible_) { - (*it)->visible_ = true; - ++g->visibleCount_; - } - } else { - break; - } - } - } -} - -void QuoteRequestsModel::showRfqsFromFront(Group *g) -{ - if (g->limit_ > 0) { - for (auto it = g->rfqs_.begin(), last = g->rfqs_.end(); it != last; ++it) { - if (g->visibleCount_ < g->limit_) { - if (!(*it)->quoted_ && !(*it)->visible_) { - (*it)->visible_ = true; - ++g->visibleCount_; - } - } else { - break; - } - } - } -} - -void QuoteRequestsModel::clearVisibleFlag(Group *g) -{ - if (g->limit_ > 0) { - for (auto it = g->rfqs_.begin(), last = g->rfqs_.end(); it != last; ++it) { - (*it)->visible_ = false; - } - - g->visibleCount_ = 0; - } -} - -void QuoteRequestsModel::onSecurityMDUpdated(const QString &security, const bs::network::MDFields &mdFields) -{ - const auto pxBid = bs::network::MDField::get(mdFields, bs::network::MDField::PriceBid); - const auto pxOffer = bs::network::MDField::get(mdFields, bs::network::MDField::PriceOffer); - if (pxBid.type != bs::network::MDField::Unknown) { - mdPrices_[security.toStdString()][Role::BidPrice] = pxBid.value; - } - if (pxOffer.type != bs::network::MDField::Unknown) { - mdPrices_[security.toStdString()][Role::OfferPrice] = pxOffer.value; - } - - if (priceUpdateInterval_ < 1) { - updatePrices(security, pxBid, pxOffer); - } else { - prices_[security] = std::make_pair(pxBid, pxOffer); - } -} diff --git a/BlockSettleUILib/Trading/QuoteRequestsModel.h b/BlockSettleUILib/Trading/QuoteRequestsModel.h deleted file mode 100644 index 4317ada35..000000000 --- a/BlockSettleUILib/Trading/QuoteRequestsModel.h +++ /dev/null @@ -1,357 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef QUOTEREQUESTSMODEL_H -#define QUOTEREQUESTSMODEL_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "CommonTypes.h" - - -namespace bs { - namespace network { - struct QuoteNotification; - } - class StatsCollector; - class SecurityStatsCollector; - class SettlementContainer; -} -class AssetManager; -class BaseCelerClient; -class ApplicationSettings; - -class QuoteRequestsModel : public QAbstractItemModel -{ - Q_OBJECT - -signals: - void quoteReqNotifStatusChanged(const bs::network::QuoteReqNotification &qrn); - void invalidateFilterModel(); - void deferredUpdate(const QPersistentModelIndex&); - -public: - enum class Column { - SecurityID = 0, - Product, - Side, - Quantity, - Party, - Status, - QuotedPx, - IndicPx, - BestPx, - Empty - }; - - enum class Role { - ReqId = Qt::UserRole, - Side, - ShowProgress, - Timeout, - TimeLeft, - BidPrice, - OfferPrice, - Grade, - AssetType, - QuotedPrice, - BestQPrice, - Product, - AllowFiltering, - HasHiddenChildren, - Quoted, - Type, - LimitOfRfqs, - QuotedRfqsCount, - Visible, - StatText, - CountOfRfqs, - SortOrder - }; - - enum class DataType { - RFQ = 0, - Group, - Market, - Unknown - }; - -public: - QuoteRequestsModel(const std::shared_ptr & - , std::shared_ptr celerClient - , std::shared_ptr appSettings - , QObject* parent); - ~QuoteRequestsModel() override; - - QuoteRequestsModel(const QuoteRequestsModel&) = delete; - QuoteRequestsModel& operator=(const QuoteRequestsModel&) = delete; - QuoteRequestsModel(QuoteRequestsModel&&) = delete; - QuoteRequestsModel& operator=(QuoteRequestsModel&&) = delete; - - void SetAssetManager(const std::shared_ptr& assetManager); - const bs::network::QuoteReqNotification &getQuoteReqNotification(const std::string &id) const; - double getPrice(const std::string &security, Role) const; - QString getMarketSecurity(const QModelIndex &index); - - void addSettlementContainer(const std::shared_ptr &); - bool StartCCSignOnOrder(const QString& orderId, QDateTime timestamp); - -public: - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &index) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - -public: - void onQuoteReqNotifReplied(const bs::network::QuoteNotification &qn); - void onQuoteNotifCancelled(const QString &reqId); - void onAllQuoteNotifCancelled(const QString &reqId); - void onQuoteReqCancelled(const QString &reqId, bool byUser); - void onQuoteRejected(const QString &reqId, const QString &reason); - void onSecurityMDUpdated(const QString &security, const bs::network::MDFields &); - void onQuoteReqNotifReceived(const bs::network::QuoteReqNotification &qrn); - void onBestQuotePrice(const QString reqId, double price, bool own); - void limitRfqs(const QModelIndex &index, int limit); - QModelIndex findMarketIndex(const QString &name) const; - void setPriceUpdateInterval(int interval); - void showQuotedRfqs(bool on = true); - -private slots: - void ticker(); - void clearModel(); - void onDeferredUpdate(const QPersistentModelIndex &index); - void onPriceUpdateTimer(); - -private: - using Prices = std::map; - using MDPrices = std::unordered_map; - - std::shared_ptr assetManager_; - std::unordered_map notifications_; - std::unordered_map> settlContainers_; - QTimer timer_; - QTimer priceUpdateTimer_; - MDPrices mdPrices_; - const QString groupNameSettlements_ = tr("Settlements"); - std::shared_ptr secStatsCollector_; - std::shared_ptr celerClient_; - std::shared_ptr appSettings_; - std::unordered_set pendingDeleteIds_; - int priceUpdateInterval_; - bool showQuoted_; - - struct IndexHelper { - IndexHelper *parent_; - void *data_; - DataType type_; - - IndexHelper() - : parent_(nullptr) - , data_(nullptr) - , type_(DataType::Unknown) - {} - - IndexHelper(IndexHelper *parent, void *data, DataType type) - : parent_(parent) - , data_(data) - , type_(type) - {} - }; - - struct Status { - QString status_; - bool showProgress_; - int timeout_; - int timeleft_; - - Status() - : showProgress_(false) - , timeout_(0) - , timeleft_(0) - {} - - Status(const QString &status, - bool showProgress, - int timeout = 0, - int timeleft = 0) - : status_(status) - , showProgress_(showProgress) - , timeout_(timeout) - , timeleft_(timeleft) - {} - }; - - struct RFQ { - QString security_; - QString product_; - QString sideString_; - QString party_; - QString quantityString_; - QString quotedPriceString_; - QString indicativePxString_; - QString bestQuotedPxString_; - Status status_; - double indicativePx_; - double quotedPrice_; - double bestQuotedPx_; - bs::network::Side::Type side_; - bs::network::Asset::Type assetType_; - std::string reqId_; - QBrush quotedPriceBrush_; - QBrush indicativePxBrush_; - QBrush stateBrush_; - IndexHelper idx_; - bool quoted_; - bool visible_; - bool withdrawn_ = false; - - RFQ() - : idx_(nullptr, this, DataType::RFQ) - , quoted_(false) - , visible_(false) - {} - - RFQ(const QString &security, - const QString &product, - const QString &sideString, - const QString &party, - const QString &quantityString, - const QString "edPriceString, - const QString &indicativePxString, - const QString &bestQuotedPxString, - const Status &status, - double indicativePx, - double quotedPrice, - double bestQuotedPx, - bs::network::Side::Type side, - bs::network::Asset::Type assetType, - const std::string &reqId) - : security_(security) - , product_(product) - , sideString_(sideString) - , party_(party) - , quantityString_(quantityString) - , quotedPriceString_(quotedPriceString) - , indicativePxString_(indicativePxString) - , bestQuotedPxString_(bestQuotedPxString) - , status_(status) - , indicativePx_(indicativePx) - , quotedPrice_(quotedPrice) - , bestQuotedPx_(bestQuotedPx) - , side_(side) - , assetType_(assetType) - , reqId_(reqId) - , idx_(nullptr, this, DataType::RFQ) - , quoted_(false) - , visible_(false) - {} - }; - - struct Group { - QString security_; - QFont font_; - std::vector> rfqs_; - IndexHelper idx_; - int limit_; - int quotedRfqsCount_; - int visibleCount_; - - Group() - : idx_(nullptr, this, DataType::Group) - , limit_(5) - , quotedRfqsCount_(0) - , visibleCount_(0) - {} - - explicit Group(const QString &security, int limit, const QFont & font = QFont()) - : security_(security) - , font_(font) - , idx_(nullptr, this, DataType::Group) - , limit_(limit) - , quotedRfqsCount_(0) - , visibleCount_(0) - {} - }; - - struct Market { - QString security_; - QFont font_; - std::vector> groups_; - IndexHelper idx_; - Group settl_; - int limit_; - - Market() - : idx_(nullptr, this, DataType::Market) - , limit_(5) - { - settl_.idx_ = idx_; - } - - explicit Market(const QString &security, int limit, const QFont & font = QFont()) - : security_(security) - , font_(font) - , idx_(nullptr, this, DataType::Market) - , limit_(limit) - { - settl_.idx_ = idx_; - } - }; - - std::vector> data_; - - struct BestQuotePrice { - double price_; - bool own_; - }; - - std::map bestQuotePrices_; - std::map> prices_; - -private: - int findGroup(IndexHelper *idx) const; - Group* findGroup(Market *market, const QString &security) const; - int findMarket(IndexHelper *idx) const; - Market* findMarket(const QString &name) const; - void updatePrices(const QString &security, const bs::network::MDField &pxBid, - const bs::network::MDField &pxOffer, std::vector> *idxs = nullptr); - void showRfqsFromBack(Group *g); - void showRfqsFromFront(Group *g); - void clearVisibleFlag(Group *g); - void updateBestQuotePrice(const QString &reqId, double price, bool own, - std::vector> *idxs = nullptr); - -private: - using cbItem = std::function; - - void insertRfq(Group *group, const bs::network::QuoteReqNotification &qrn); - void forSpecificId(const std::string &, const cbItem &); - void forEachSecurity(const QString &, const cbItem &); - void setStatus(const std::string &reqId, bs::network::QuoteReqNotification::Status, const QString &details = {}); - void updateSettlementCounters(); - void deleteSettlement(const std::string &id); - static QString quoteReqStatusDesc(bs::network::QuoteReqNotification::Status status); - static QBrush bgColorForStatus(bs::network::QuoteReqNotification::Status status); - static QBrush colorForQuotedPrice(double quotedPx, double bestQuotedPx, bool own = false); -}; - -#endif // QUOTEREQUESTSMODEL_H diff --git a/BlockSettleUILib/Trading/QuoteRequestsWidget.cpp b/BlockSettleUILib/Trading/QuoteRequestsWidget.cpp deleted file mode 100644 index 9b10b324a..000000000 --- a/BlockSettleUILib/Trading/QuoteRequestsWidget.cpp +++ /dev/null @@ -1,548 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "QuoteRequestsWidget.h" -#include "ui_QuoteRequestsWidget.h" -#include - -#include "AssetManager.h" -#include "CurrencyPair.h" -#include "NotificationCenter.h" -#include "QuoteProvider.h" -#include "RFQBlotterTreeView.h" -#include "SettlementContainer.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" - -#include -#include -#include -#include - -namespace bs { - -void StatsCollector::saveState() -{ -} - -} /* namespace bs */ - - -// -// DoNotDrawSelectionDelegate -// - -//! This delegate just clears selection bit and paints item as -//! unselected always. -class DoNotDrawSelectionDelegate final : public QStyledItemDelegate -{ -public: - explicit DoNotDrawSelectionDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - - void paint(QPainter *painter, const QStyleOptionViewItem &opt, - const QModelIndex &index) const override - { - QStyleOptionViewItem changedOpt = opt; - changedOpt.state &= ~(QStyle::State_Selected); - - QStyledItemDelegate::paint(painter, changedOpt, index); - } -}; // class DoNotDrawSelectionDelegate - - -QuoteRequestsWidget::QuoteRequestsWidget(QWidget* parent) - : QWidget(parent) - , ui_(new Ui::QuoteRequestsWidget()) - , model_(nullptr) - , sortModel_(nullptr) -{ - ui_->setupUi(this); - ui_->treeViewQuoteRequests->setUniformRowHeights(true); - - connect(ui_->treeViewQuoteRequests, &QTreeView::clicked, this, &QuoteRequestsWidget::onQuoteReqNotifSelected); - connect(ui_->treeViewQuoteRequests, &QTreeView::doubleClicked, this, &QuoteRequestsWidget::onQuoteReqNotifSelected); -} - -QuoteRequestsWidget::~QuoteRequestsWidget() = default; - -void QuoteRequestsWidget::init(std::shared_ptr logger, const std::shared_ptr "eProvider - , const std::shared_ptr& assetManager, const std::shared_ptr &statsCollector - , const std::shared_ptr &appSettings, std::shared_ptr celerClient) -{ - logger_ = logger; - assetManager_ = assetManager; - appSettings_ = appSettings; - collapsed_ = appSettings_->get(ApplicationSettings::Filter_MD_QN); - dropQN_ = appSettings->get(ApplicationSettings::dropQN); - - model_ = new QuoteRequestsModel(statsCollector, celerClient, appSettings_, - ui_->treeViewQuoteRequests); - model_->SetAssetManager(assetManager); - - sortModel_ = new QuoteReqSortModel(model_, this); - sortModel_->setSourceModel(model_); - sortModel_->showQuoted(appSettings_->get(ApplicationSettings::ShowQuoted)); - - ui_->treeViewQuoteRequests->setModel(sortModel_); - ui_->treeViewQuoteRequests->setRfqModel(model_); - ui_->treeViewQuoteRequests->setSortModel(sortModel_); - ui_->treeViewQuoteRequests->setAppSettings(appSettings_); - ui_->treeViewQuoteRequests->header()->setSectionResizeMode( - static_cast(QuoteRequestsModel::Column::SecurityID), - QHeaderView::ResizeToContents); - - connect(ui_->treeViewQuoteRequests, &QTreeView::collapsed, - this, &QuoteRequestsWidget::onCollapsed); - connect(ui_->treeViewQuoteRequests, &QTreeView::expanded, - this, &QuoteRequestsWidget::onExpanded); - connect(model_, &QuoteRequestsModel::quoteReqNotifStatusChanged, [this](const bs::network::QuoteReqNotification &qrn) { - emit quoteReqNotifStatusChanged(qrn); - }); - connect(model_, &QAbstractItemModel::rowsInserted, this, &QuoteRequestsWidget::onRowsInserted); - connect(model_, &QAbstractItemModel::rowsRemoved, this, &QuoteRequestsWidget::onRowsRemoved); - connect(sortModel_, &QSortFilterProxyModel::rowsInserted, this, &QuoteRequestsWidget::onRowsChanged); - connect(sortModel_, &QSortFilterProxyModel::rowsRemoved, this, &QuoteRequestsWidget::onRowsChanged); - - connect(quoteProvider.get(), &QuoteProvider::quoteReqNotifReceived, this, &QuoteRequestsWidget::onQuoteRequest); - connect(appSettings.get(), &ApplicationSettings::settingChanged, this, &QuoteRequestsWidget::onSettingChanged); - - ui_->treeViewQuoteRequests->setItemDelegateForColumn( - static_cast(QuoteRequestsModel::Column::Status), new RequestsProgressDelegate(ui_->treeViewQuoteRequests)); - - auto *doNotDrawSelectionDelegate = new DoNotDrawSelectionDelegate(ui_->treeViewQuoteRequests); - ui_->treeViewQuoteRequests->setItemDelegateForColumn( - static_cast(QuoteRequestsModel::Column::QuotedPx), - doNotDrawSelectionDelegate); - ui_->treeViewQuoteRequests->setItemDelegateForColumn( - static_cast(QuoteRequestsModel::Column::IndicPx), - doNotDrawSelectionDelegate); - ui_->treeViewQuoteRequests->setItemDelegateForColumn( - static_cast(QuoteRequestsModel::Column::BestPx), - doNotDrawSelectionDelegate); - ui_->treeViewQuoteRequests->setItemDelegateForColumn( - static_cast(QuoteRequestsModel::Column::Empty), - doNotDrawSelectionDelegate); - - const auto opt = ui_->treeViewQuoteRequests->viewOptions(); - const int width = opt.fontMetrics.boundingRect(tr("No quote received")).width() + 10; - ui_->treeViewQuoteRequests->header()->resizeSection( - static_cast(QuoteRequestsModel::Column::Status), - width); -} - -void QuoteRequestsWidget::onQuoteReqNotifSelected(const QModelIndex& index) -{ - const auto quoteIndex = sortModel_->index(index.row(), 0, index.parent()); - std::string qId = sortModel_->data(quoteIndex, - static_cast(QuoteRequestsModel::Role::ReqId)).toString().toStdString(); - const bs::network::QuoteReqNotification &qrn = model_->getQuoteReqNotification(qId); - - double bidPx = model_->getPrice(qrn.security, QuoteRequestsModel::Role::BidPrice); - double offerPx = model_->getPrice(qrn.security, QuoteRequestsModel::Role::OfferPrice); - const double bestQPx = sortModel_->data(quoteIndex, - static_cast(QuoteRequestsModel::Role::BestQPrice)).toDouble(); - if (!qFuzzyIsNull(bestQPx)) { - CurrencyPair cp(qrn.security); - bool isBuy = (qrn.side == bs::network::Side::Buy) ^ (cp.NumCurrency() == qrn.product); - const double quotedPx = sortModel_->data(quoteIndex, - static_cast(QuoteRequestsModel::Role::QuotedPrice)).toDouble(); - auto assetType = assetManager_->GetAssetTypeForSecurity(qrn.security); - const auto pip = qFuzzyCompare(bestQPx, quotedPx) ? 0.0 : std::pow(10, -UiUtils::GetPricePrecisionForAssetType(assetType)); - if (isBuy) { - bidPx = bestQPx + pip; - } - else { - offerPx = bestQPx - pip; - } - } - - const QString productGroup = model_->getMarketSecurity(sortModel_->mapToSource(index)); - - emit Selected(productGroup, qrn, bidPx, offerPx); -} - -void QuoteRequestsWidget::addSettlementContainer(const std::shared_ptr &container) -{ - if (model_) { - model_->addSettlementContainer(container); - } -} - -bool QuoteRequestsWidget::StartCCSignOnOrder(const QString& orderId, QDateTime timestamp) -{ - if (model_) { - return model_->StartCCSignOnOrder(orderId, timestamp); - } - - return false; -} - -RFQBlotterTreeView* QuoteRequestsWidget::view() const -{ - return ui_->treeViewQuoteRequests; -} - -void QuoteRequestsWidget::onQuoteReqNotifReplied(const bs::network::QuoteNotification &qn) -{ - if (model_) { - model_->onQuoteReqNotifReplied(qn); - } -} - -void QuoteRequestsWidget::onQuoteNotifCancelled(const QString &reqId) -{ - if (model_) { - model_->onQuoteNotifCancelled(reqId); - } -} - -void QuoteRequestsWidget::onAllQuoteNotifCancelled(const QString &reqId) -{ - if (model_) { - model_->onAllQuoteNotifCancelled(reqId); - } -} - -void QuoteRequestsWidget::onQuoteReqCancelled(const QString &reqId, bool byUser) -{ - if (model_) { - model_->onQuoteReqCancelled(reqId, byUser); - } -} - -void QuoteRequestsWidget::onQuoteRejected(const QString &reqId, const QString &reason) -{ - if (model_) { - model_->onQuoteRejected(reqId, reason); - } -} - -void QuoteRequestsWidget::onBestQuotePrice(const QString reqId, double price, bool own) -{ - if (model_) { - model_->onBestQuotePrice(reqId, price, own); - } -} - -void QuoteRequestsWidget::onSecurityMDUpdated(bs::network::Asset::Type assetType, const QString &security, bs::network::MDFields mdFields) -{ - Q_UNUSED(assetType); - if (model_ && !mdFields.empty()) { - model_->onSecurityMDUpdated(security, mdFields); - } -} - -void QuoteRequestsWidget::onQuoteRequest(const bs::network::QuoteReqNotification &qrn) -{ - const bool includeZc = - (qrn.assetType == bs::network::Asset::SpotXBT) - ? bs::UTXOReservationManager::kIncludeZcDealerSpotXbt - : bs::UTXOReservationManager::kIncludeZcDealerCc; - - if (dropQN_) { - bool checkResult = true; - if (qrn.side == bs::network::Side::Buy) { - checkResult = assetManager_->checkBalance(qrn.product, qrn.quantity, includeZc); - } - else { - CurrencyPair cp(qrn.security); - checkResult = assetManager_->checkBalance(cp.ContraCurrency(qrn.product), 0.01, includeZc); - } - if (!checkResult) { - return; - } - } - if (model_ != nullptr) { - model_->onQuoteReqNotifReceived(qrn); - } -} - -void QuoteRequestsWidget::onSettingChanged(int setting, QVariant val) -{ - switch (static_cast(setting)) - { - case ApplicationSettings::dropQN: - dropQN_ = val.toBool(); - break; - - case ApplicationSettings::FxRfqLimit : - ui_->treeViewQuoteRequests->setLimit(ApplicationSettings::FxRfqLimit, val.toInt()); - break; - - case ApplicationSettings::XbtRfqLimit : - ui_->treeViewQuoteRequests->setLimit(ApplicationSettings::XbtRfqLimit, val.toInt()); - break; - - case ApplicationSettings::PmRfqLimit : - ui_->treeViewQuoteRequests->setLimit(ApplicationSettings::PmRfqLimit, val.toInt()); - break; - - case ApplicationSettings::PriceUpdateInterval : - model_->setPriceUpdateInterval(val.toInt()); - break; - - case ApplicationSettings::ShowQuoted : - sortModel_->showQuoted(val.toBool()); - break; - - default: - break; - } -} - -void QuoteRequestsWidget::onRowsChanged() -{ - unsigned int cntChildren = 0; - for (int row = 0; row < sortModel_->rowCount(); row++) { - cntChildren += sortModel_->rowCount(sortModel_->index(row, 0)); - } - NotificationCenter::notify(bs::ui::NotifyType::DealerQuotes, { cntChildren }); -} - -void QuoteRequestsWidget::onRowsInserted(const QModelIndex &parent, int first, int last) -{ - for (int row = first; row <= last; row++) { - const auto &index = model_->index(row, 0, parent); - if (index.data(static_cast(QuoteRequestsModel::Role::ReqId)).isNull()) { - expandIfNeeded(); - } - else { - for (int i = 1; i < sortModel_->columnCount(); ++i) { - if (i != static_cast(QuoteRequestsModel::Column::Status)) { - ui_->treeViewQuoteRequests->resizeColumnToContents(i); - } - } - } - } -} - -void QuoteRequestsWidget::onRowsRemoved(const QModelIndex &, int, int) -{ - const auto &indices = ui_->treeViewQuoteRequests->selectionModel()->selectedIndexes(); - if (indices.isEmpty()) { - emit Selected({}, bs::network::QuoteReqNotification(), 0, 0); - } - else { - onQuoteReqNotifSelected(indices.first()); - } -} - -void QuoteRequestsWidget::onCollapsed(const QModelIndex &index) -{ - if (index.isValid()) { - collapsed_.append(UiUtils::modelPath(sortModel_->mapToSource(index), model_)); - saveCollapsedState(); - } -} - -void QuoteRequestsWidget::onExpanded(const QModelIndex &index) -{ - if (index.isValid()) { - collapsed_.removeOne(UiUtils::modelPath(sortModel_->mapToSource(index), model_)); - saveCollapsedState(); - } -} - -void QuoteRequestsWidget::saveCollapsedState() -{ - appSettings_->set(ApplicationSettings::Filter_MD_QN, collapsed_); -} - -void QuoteRequestsWidget::expandIfNeeded(const QModelIndex &index) -{ - if (!collapsed_.contains(UiUtils::modelPath(sortModel_->mapToSource(index), model_))) - ui_->treeViewQuoteRequests->expand(index); - - for (int i = 0; i < sortModel_->rowCount(index); ++i) - expandIfNeeded(sortModel_->index(i, 0, index)); -} - -bs::SecurityStatsCollector::SecurityStatsCollector(const std::shared_ptr appSettings, ApplicationSettings::Setting param) - : appSettings_(appSettings), param_(param) -{ - const auto map = appSettings_->get(param); - for (auto it = map.begin(); it != map.end(); ++it) { - counters_[it.key().toStdString()] = it.value().toUInt(); - } - connect(appSettings.get(), &ApplicationSettings::settingChanged, this, &bs::SecurityStatsCollector::onSettingChanged); - - gradeColors_ = { QColor(Qt::white), QColor(Qt::lightGray), QColor(Qt::gray), QColor(Qt::darkGray) }; - gradeBoundary_.resize(gradeColors_.size(), 0); - - timer_.setInterval(60 * 1000); // once in a minute - connect(&timer_, &QTimer::timeout, this, &bs::SecurityStatsCollector::saveState); - timer_.start(); -} - -void bs::SecurityStatsCollector::saveState() -{ - if (!modified_) { - return; - } - QVariantMap map; - for (const auto counter : counters_) { - map[QString::fromStdString(counter.first)] = counter.second; - } - appSettings_->set(param_, map); - modified_ = false; -} - -void bs::SecurityStatsCollector::onSettingChanged(int setting, QVariant val) -{ - if ((static_cast(setting) == param_) && val.toMap().empty()) { - resetCounters(); - } -} - -void bs::SecurityStatsCollector::onQuoteSubmitted(const bs::network::QuoteNotification &qn) -{ - counters_[qn.security]++; - modified_ = true; - recalculate(); -} - -unsigned int bs::SecurityStatsCollector::getGradeFor(const std::string &security) const -{ - const auto itSec = counters_.find(security); - if (itSec == counters_.end()) { - return gradeBoundary_.size() - 1; - } - for (size_t i = 0; i < gradeBoundary_.size(); i++) { - if (itSec->second <= gradeBoundary_[i]) { - return gradeBoundary_.size() - 1 - i; - } - } - return 0; -} - -QColor bs::SecurityStatsCollector::getColorFor(const std::string &security) const -{ - return gradeColors_[getGradeFor(security)]; -} - -void bs::SecurityStatsCollector::resetCounters() -{ - counters_.clear(); - modified_ = true; - recalculate(); -} - -void bs::SecurityStatsCollector::recalculate() -{ - std::vector counts; - for (const auto &counter : counters_) { - counts.push_back(counter.second); - } - std::sort(counts.begin(), counts.end()); - - if (counts.size() < gradeBoundary_.size()) { - for (unsigned int i = 0; i < counts.size(); i++) { - gradeBoundary_[gradeBoundary_.size() - counts.size() + i] = counts[i]; - } - } - else { - const unsigned int step = counts.size() / gradeBoundary_.size(); - for (unsigned int i = 0; i < gradeBoundary_.size(); i++) { - gradeBoundary_[i] = counts[qMin((i + 1) * step, counts.size() - 1)]; - } - } -} - - -QColor bs::SettlementStatsCollector::getColorFor(const std::string &) const -{ - return QColor(Qt::cyan); -} - -unsigned int bs::SettlementStatsCollector::getGradeFor(const std::string &) const -{ - return container_->timeLeftMs(); -} - - -QuoteReqSortModel::QuoteReqSortModel(QuoteRequestsModel *model, QObject *parent) - : QSortFilterProxyModel(parent) - , model_(model) - , showQuoted_(true) -{ - connect(model_, &QuoteRequestsModel::invalidateFilterModel, - this, &QuoteReqSortModel::invalidateFilter); -} - -bool QuoteReqSortModel::filterAcceptsRow(int row, const QModelIndex &parent) const -{ - const auto index = sourceModel()->index(row, 0, parent); - - if (!index.isValid()) { - return false; - } - - if (index.data(static_cast(QuoteRequestsModel::Role::Type)).toInt() != - static_cast(QuoteRequestsModel::DataType::RFQ)) { - return true; - } - - if (parent.data(static_cast(QuoteRequestsModel::Role::LimitOfRfqs)).toInt() <= 0) { - return true; - } - - if (index.data(static_cast(QuoteRequestsModel::Role::Visible)).toBool()) { - return true; - } - else if (index.data(static_cast(QuoteRequestsModel::Role::Quoted)).toBool() && - showQuoted_) { - return true; - } - - return false; -} - -void QuoteReqSortModel::showQuoted(bool on) -{ - if (showQuoted_ != on) { - showQuoted_ = on; - - model_->showQuotedRfqs(on); - - invalidateFilter(); - } -} - -bool QuoteReqSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - const auto leftGrade = sourceModel()->data(left, - static_cast(QuoteRequestsModel::Role::Grade)); - const auto rightGrade = sourceModel()->data(right, - static_cast(QuoteRequestsModel::Role::Grade)); - if (leftGrade != rightGrade) { - return (leftGrade < rightGrade); - } - const auto leftTL = sourceModel()->data(left, - static_cast(QuoteRequestsModel::Role::SortOrder)); - const auto rightTL = sourceModel()->data(right, - static_cast(QuoteRequestsModel::Role::SortOrder)); - - return (leftTL < rightTL); -} - -bool RequestsProgressDelegate::isDrawProgressBar(const QModelIndex& index) const -{ - return index.data(static_cast(QuoteRequestsModel::Role::ShowProgress)).toBool(); -} - -int RequestsProgressDelegate::maxValue(const QModelIndex& index) const -{ - return index.data(static_cast(QuoteRequestsModel::Role::Timeout)).toInt(); -} - -int RequestsProgressDelegate::currentValue(const QModelIndex& index) const -{ - return index.data(static_cast(QuoteRequestsModel::Role::TimeLeft)).toInt(); -} diff --git a/BlockSettleUILib/Trading/QuoteRequestsWidget.h b/BlockSettleUILib/Trading/QuoteRequestsWidget.h deleted file mode 100644 index bf580d8e0..000000000 --- a/BlockSettleUILib/Trading/QuoteRequestsWidget.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef QUOTE_REQUESTS_WIDGET_H -#define QUOTE_REQUESTS_WIDGET_H - -#include "ApplicationSettings.h" -#include "QuoteRequestsModel.h" -#include "ProgressViewDelegateBase.h" - -#include -#include -#include -#include - -#include -#include -#include - -namespace Ui { - class QuoteRequestsWidget; -}; -namespace spdlog { - class logger; -} -class QuoteProvider; - -namespace bs { - class SettlementContainer; - namespace network { - struct QuoteReqNotification; - struct QuoteNotification; - } - - class StatsCollector - { - public: - virtual ~StatsCollector() noexcept = default; - virtual QColor getColorFor(const std::string &key) const = 0; - virtual unsigned int getGradeFor(const std::string &key) const = 0; - virtual void saveState(); - }; - - class SecurityStatsCollector : public QObject, public StatsCollector - { - Q_OBJECT - public: - SecurityStatsCollector(const std::shared_ptr, ApplicationSettings::Setting param); - - QColor getColorFor(const std::string &key) const override; - unsigned int getGradeFor(const std::string &key) const override; - - void saveState() override; - - public slots: - void onQuoteSubmitted(const network::QuoteNotification &); - - private slots: - void onSettingChanged(int setting, QVariant val); - - private: - std::shared_ptr appSettings_; - ApplicationSettings::Setting param_; - std::unordered_map counters_; - std::vector gradeColors_; - std::vector gradeBoundary_; - QTimer timer_; - bool modified_ = false; - - private: - void recalculate(); - void resetCounters(); - }; - - class SettlementStatsCollector : public StatsCollector - { - public: - SettlementStatsCollector(const std::shared_ptr &container) - : container_(container) {} - QColor getColorFor(const std::string &key) const override; - unsigned int getGradeFor(const std::string &key) const override; - - private: - std::shared_ptr container_; - }; - -} // namespace bs - - -class RequestsProgressDelegate : public ProgressViewDelegateBase -{ -public: - explicit RequestsProgressDelegate(QWidget* parent = nullptr) - : ProgressViewDelegateBase(parent) - {} - ~RequestsProgressDelegate() override = default; -protected: - bool isDrawProgressBar(const QModelIndex& index) const override; - int maxValue(const QModelIndex& index) const override; - int currentValue(const QModelIndex& index) const override; -}; - - -class AssetManager; -class BaseCelerClient; -class QuoteRequestsModel; -class QuoteReqSortModel; -class RFQBlotterTreeView; -class BaseCelerClient; - -class QuoteRequestsWidget : public QWidget -{ -Q_OBJECT - -public: - QuoteRequestsWidget(QWidget* parent = nullptr); - ~QuoteRequestsWidget() override; - - void init(std::shared_ptr logger, const std::shared_ptr "eProvider - , const std::shared_ptr& assetManager, const std::shared_ptr &statsCollector - , const std::shared_ptr &appSettings - , std::shared_ptr celerClient); - - void addSettlementContainer(const std::shared_ptr &); - bool StartCCSignOnOrder(const QString& orderId, QDateTime timestamp); - - RFQBlotterTreeView* view() const; - -signals: - void Selected(const QString& productGroup, const bs::network::QuoteReqNotification& qrc, double indicBid, double indicAsk); - void quoteReqNotifStatusChanged(const bs::network::QuoteReqNotification &qrn); - -public slots: - void onQuoteReqNotifReplied(const bs::network::QuoteNotification &); - void onQuoteReqNotifSelected(const QModelIndex& index); - void onQuoteNotifCancelled(const QString &reqId); - void onAllQuoteNotifCancelled(const QString &reqId); - void onQuoteReqCancelled(const QString &reqId, bool byUser); - void onQuoteRejected(const QString &reqId, const QString &reason); - void onSecurityMDUpdated(bs::network::Asset::Type, const QString &security, bs::network::MDFields); - void onBestQuotePrice(const QString reqId, double price, bool own); - -private slots: - void onSettingChanged(int setting, QVariant val); - void onQuoteRequest(const bs::network::QuoteReqNotification &qrn); - void onRowsChanged(); - void onRowsInserted(const QModelIndex &parent, int first, int last); - void onRowsRemoved(const QModelIndex &parent, int first, int last); - void onCollapsed(const QModelIndex &index); - void onExpanded(const QModelIndex &index); - -private: - void expandIfNeeded(const QModelIndex &index = QModelIndex()); - void saveCollapsedState(); - -private: - std::unique_ptr ui_; - std::shared_ptr logger_; - std::shared_ptr assetManager_; - std::shared_ptr appSettings_; - QStringList collapsed_; - QuoteRequestsModel * model_; - QuoteReqSortModel * sortModel_; - bool dropQN_ = false; -}; - - -class QuoteReqSortModel : public QSortFilterProxyModel -{ - Q_OBJECT -public: - QuoteReqSortModel(QuoteRequestsModel *model, QObject *parent); - - void showQuoted(bool on = true); - -protected: - bool filterAcceptsRow(int row, const QModelIndex &parent) const override; - bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - -private: - QuoteRequestsModel * model_; - bool showQuoted_; -}; - -#endif // QUOTE_REQUESTS_WIDGET_H diff --git a/BlockSettleUILib/Trading/QuoteRequestsWidget.ui b/BlockSettleUILib/Trading/QuoteRequestsWidget.ui deleted file mode 100644 index d419fb48b..000000000 --- a/BlockSettleUILib/Trading/QuoteRequestsWidget.ui +++ /dev/null @@ -1,69 +0,0 @@ - - - - QuoteRequestsWidget - - - - 0 - 0 - 629 - 240 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QAbstractItemView::NoEditTriggers - - - true - - - true - - - true - - - true - - - - - - - - RFQBlotterTreeView - QTreeView -
RFQBlotterTreeView.h
-
-
- - -
diff --git a/BlockSettleUILib/Trading/RFQBlotterTreeView.cpp b/BlockSettleUILib/Trading/RFQBlotterTreeView.cpp deleted file mode 100644 index 59141fb5c..000000000 --- a/BlockSettleUILib/Trading/RFQBlotterTreeView.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ - -#include "RFQBlotterTreeView.h" -#include "QuoteRequestsModel.h" -#include "QuoteRequestsWidget.h" -#include "UiUtils.h" - -#include -#include -#include -#include - - -// -// RFQBlotterTreeView -// - -RFQBlotterTreeView::RFQBlotterTreeView(QWidget *parent) - : TreeViewWithEnterKey (parent) - , model_(nullptr) - , sortModel_(nullptr) -{ -} - -void RFQBlotterTreeView::setRfqModel(QuoteRequestsModel *model) -{ - model_ = model; -} - -void RFQBlotterTreeView::setSortModel(QuoteReqSortModel *model) -{ - sortModel_ = model; -} - -void RFQBlotterTreeView::setAppSettings(std::shared_ptr appSettings) -{ - appSettings_ = appSettings; -} - -void RFQBlotterTreeView::setLimit(ApplicationSettings::Setting s, int limit) -{ - setLimit(findMarket(UiUtils::marketNameForLimit(s)), limit); -} - -void RFQBlotterTreeView::contextMenuEvent(QContextMenuEvent *e) -{ - auto index = currentIndex(); - - if (index.isValid()) { - QMenu menu(this); - QMenu limit(tr("Limit To"), &menu); - limit.addAction(tr("1 RFQ"), [this] () { this->setLimit(1); }); - limit.addAction(tr("3 RFQs"), [this] () { this->setLimit(3); }); - limit.addAction(tr("5 RFQs"), [this] () { this->setLimit(5); }); - limit.addAction(tr("10 RFQs"), [this] () { this->setLimit(10); }); - menu.addMenu(&limit); - - if (index.data(static_cast(QuoteRequestsModel::Role::LimitOfRfqs)).toInt() > 0) { - menu.addSeparator(); - menu.addAction(tr("All RFQs"), [this] () { this->setLimit(-1); }); - } - - menu.exec(e->globalPos()); - e->accept(); - } else { - e->ignore(); - } -} - -void RFQBlotterTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QTreeView::drawRow(painter, option, index); - - if(index.data(static_cast(QuoteRequestsModel::Role::Type)).toInt() == - static_cast(QuoteRequestsModel::DataType::Group)) { - int left = header()->sectionViewportPosition( - static_cast(QuoteRequestsModel::Column::Product)); - int right = header()->sectionViewportPosition( - static_cast(QuoteRequestsModel::Column::Empty)) + - header()->sectionSize(static_cast(QuoteRequestsModel::Column::Empty)); - - QRect r = option.rect; - r.setX(left); - r.setWidth(right - left); - - painter->save(); - painter->setFont(option.font); - painter->setPen(QColor(0x00, 0xA9, 0xE3)); - - if (isExpanded(index)) { - painter->drawText(r, 0, - index.data(static_cast(QuoteRequestsModel::Role::StatText)).toString()); - } else { - painter->drawText(r, 0, - tr("0 of %1").arg( - index.data(static_cast(QuoteRequestsModel::Role::CountOfRfqs)).toInt())); - } - - painter->restore(); - } -} - -void RFQBlotterTreeView::setLimit(const QModelIndex &index, int limit) -{ - if (index.isValid()) { - model_->limitRfqs(index, limit); - } -} - -void RFQBlotterTreeView::setLimit(int limit) -{ - auto index = sortModel_->mapToSource(currentIndex()); - - if (index.isValid()) { - while (index.parent().isValid()) { - index = index.parent(); - } - - appSettings_->set(UiUtils::limitRfqSetting(index.data().toString()), limit); - - setLimit(index, limit); - } -} - -QModelIndex RFQBlotterTreeView::findMarket(const QString &name) const -{ - return model_->findMarketIndex(name); -} diff --git a/BlockSettleUILib/Trading/RFQBlotterTreeView.h b/BlockSettleUILib/Trading/RFQBlotterTreeView.h deleted file mode 100644 index b726ef2ab..000000000 --- a/BlockSettleUILib/Trading/RFQBlotterTreeView.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ - -#ifndef RFQBLOTTERTREEVIEW_H_INCLUDED -#define RFQBLOTTERTREEVIEW_H_INCLUDED - -#include "TreeViewWithEnterKey.h" -#include "ApplicationSettings.h" - -#include - - -class QuoteRequestsModel; -class QuoteReqSortModel; - -// -// RFQBlotterTreeView -// - -//! Tree view for RFQ blotter. -class RFQBlotterTreeView : public TreeViewWithEnterKey -{ - Q_OBJECT - -public: - RFQBlotterTreeView(QWidget *parent); - ~RFQBlotterTreeView() noexcept override = default; - - void setRfqModel(QuoteRequestsModel *model); - void setSortModel(QuoteReqSortModel *model); - void setAppSettings(std::shared_ptr appSettings); - - void setLimit(ApplicationSettings::Setting s, int limit); - -protected: - void contextMenuEvent(QContextMenuEvent *e) override; - void drawRow(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - void setLimit(const QModelIndex &index, int limit); - void setLimit(int limit); - QModelIndex findMarket(const QString &name) const; - -private: - QuoteRequestsModel * model_; - QuoteReqSortModel *sortModel_; - std::shared_ptr appSettings_; -}; // class RFQBlotterTreeView - -#endif // RFQBLOTTERTREEVIEW_H_INCLUDED diff --git a/BlockSettleUILib/Trading/RFQDealerReply.cpp b/BlockSettleUILib/Trading/RFQDealerReply.cpp deleted file mode 100644 index 268bcb7a5..000000000 --- a/BlockSettleUILib/Trading/RFQDealerReply.cpp +++ /dev/null @@ -1,1321 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RFQDealerReply.h" -#include "ui_RFQDealerReply.h" - -#include - -#include - -#include -#include - -#include "ApplicationSettings.h" -#include "AssetManager.h" -#include "AuthAddressManager.h" -#include "AutoSignQuoteProvider.h" -#include "BSErrorCodeStrings.h" -#include "BSMessageBox.h" -#include "CoinControlDialog.h" -#include "CoinControlWidget.h" -#include "CurrencyPair.h" -#include "CustomControls/CustomComboBox.h" -#include "FastLock.h" -#include "QuoteProvider.h" -#include "SelectedTransactionInputs.h" -#include "SignContainer.h" -#include "TradesUtils.h" -#include "TxClasses.h" -#include "UiUtils.h" -#include "UserScriptRunner.h" -#include "UtxoReservationManager.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" - -namespace { - const QString kNoBalanceAvailable = QLatin1String("-"); - const QString kReservedBalance = QLatin1String("Reserved input balance"); - const QString kAvailableBalance = QLatin1String("Available balance"); -} - -using namespace bs::ui; - -RFQDealerReply::RFQDealerReply(QWidget* parent) - : QWidget(parent) - , ui_(new Ui::RFQDealerReply()) -{ - ui_->setupUi(this); - initUi(); - - connect(ui_->spinBoxBidPx, static_cast(&QDoubleSpinBox::valueChanged), this, &RFQDealerReply::priceChanged); - connect(ui_->spinBoxOfferPx, static_cast(&QDoubleSpinBox::valueChanged), this, &RFQDealerReply::priceChanged); - - ui_->spinBoxBidPx->installEventFilter(this); - ui_->spinBoxOfferPx->installEventFilter(this); - - connect(ui_->pushButtonSubmit, &QPushButton::clicked, this, &RFQDealerReply::submitButtonClicked); - connect(ui_->pushButtonPull, &QPushButton::clicked, this, &RFQDealerReply::pullButtonClicked); - connect(ui_->toolButtonXBTInputsSend, &QPushButton::clicked, this, &RFQDealerReply::showCoinControl); - - connect(ui_->comboBoxXbtWallet, qOverload(&QComboBox::currentIndexChanged), this, &RFQDealerReply::walletSelected); - connect(ui_->authenticationAddressComboBox, qOverload(&QComboBox::currentIndexChanged), this, &RFQDealerReply::onAuthAddrChanged); - - ui_->groupBoxSettlementInputs->hide(); -} - -RFQDealerReply::~RFQDealerReply() = default; - -void RFQDealerReply::init(const std::shared_ptr logger - , const std::shared_ptr &authAddressManager - , const std::shared_ptr& assetManager - , const std::shared_ptr& quoteProvider - , const std::shared_ptr &appSettings - , const std::shared_ptr &connectionManager - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &autoSignProvider - , const std::shared_ptr &utxoReservationManager) -{ - logger_ = logger; - assetManager_ = assetManager; - quoteProvider_ = quoteProvider; - authAddressManager_ = authAddressManager; - appSettings_ = appSettings; - signingContainer_ = container; - armory_ = armory; - connectionManager_ = connectionManager; - autoSignProvider_ = autoSignProvider; - utxoReservationManager_ = utxoReservationManager; - - connect((AQScriptRunner *)autoSignProvider_->scriptRunner(), &AQScriptRunner::sendQuote - , this, &RFQDealerReply::onAQReply, Qt::QueuedConnection); - connect((AQScriptRunner *)autoSignProvider_->scriptRunner(), &AQScriptRunner::pullQuoteNotif - , this, &RFQDealerReply::pullQuoteNotif, Qt::QueuedConnection); - - connect(autoSignProvider_.get(), &AutoSignScriptProvider::autoSignStateChanged, - this, &RFQDealerReply::onAutoSignStateChanged, Qt::QueuedConnection); - connect(utxoReservationManager_.get(), &bs::UTXOReservationManager::availableUtxoChanged, - this, &RFQDealerReply::onUTXOReservationChanged); -} - -void RFQDealerReply::initUi() -{ - invalidBalanceFont_ = ui_->labelBalanceValue->font(); - invalidBalanceFont_.setStrikeOut(true); - - ui_->authenticationAddressLabel->hide(); - ui_->authenticationAddressComboBox->hide(); - ui_->pushButtonSubmit->setEnabled(false); - ui_->pushButtonPull->setEnabled(false); - ui_->widgetWallet->hide(); - - ui_->spinBoxBidPx->clear(); - ui_->spinBoxOfferPx->clear(); - ui_->spinBoxBidPx->setEnabled(false); - ui_->spinBoxOfferPx->setEnabled(false); - - ui_->labelProductGroup->clear(); - - validateGUI(); -} - -void RFQDealerReply::setWalletsManager(const std::shared_ptr &walletsManager) -{ - walletsManager_ = walletsManager; - validateGUI(); - - connect(walletsManager_.get(), &bs::sync::WalletsManager::CCLeafCreated, this, &RFQDealerReply::onHDLeafCreated); - connect(walletsManager_.get(), &bs::sync::WalletsManager::CCLeafCreateFailed, this, &RFQDealerReply::onCreateHDWalletError); - - if (autoSignProvider_->scriptRunner()) { - autoSignProvider_->scriptRunner()->setWalletsManager(walletsManager_); - } - - auto updateAuthAddresses = [this] { - UiUtils::fillAuthAddressesComboBoxWithSubmitted(ui_->authenticationAddressComboBox, authAddressManager_); - onAuthAddrChanged(ui_->authenticationAddressComboBox->currentIndex()); - }; - updateAuthAddresses(); - connect(authAddressManager_.get(), &AuthAddressManager::AddressListUpdated, this, updateAuthAddresses); -} - -CustomDoubleSpinBox* RFQDealerReply::bidSpinBox() const -{ - return ui_->spinBoxBidPx; -} - -CustomDoubleSpinBox* RFQDealerReply::offerSpinBox() const -{ - return ui_->spinBoxOfferPx; -} - -QPushButton* RFQDealerReply::pullButton() const -{ - return ui_->pushButtonPull; -} - -QPushButton* RFQDealerReply::quoteButton() const -{ - return ui_->pushButtonSubmit; -} - -void RFQDealerReply::updateRespQuantity() -{ - if (currentQRN_.empty()) { - ui_->labelRespQty->clear(); - return; - } - - if (product_ == bs::network::XbtCurrency) { - ui_->labelRespQty->setText(UiUtils::displayAmount(getValue())); - } else { - ui_->labelRespQty->setText(UiUtils::displayCurrencyAmount(getValue())); - } -} - -void RFQDealerReply::reset() -{ - payInRecipId_ = UINT_MAX; - if (currentQRN_.empty()) { - ui_->labelProductGroup->clear(); - ui_->labelSecurity->clear(); - ui_->labelReqProduct->clear(); - ui_->labelReqSide->clear(); - ui_->labelReqQty->clear(); - ui_->labelRespProduct->clear(); - ui_->labelQuantity->clear(); - ui_->labelProduct->clear(); - indicBid_ = indicAsk_ = 0; - selectedXbtInputs_.clear(); - selectedXbtRes_.release(); - setBalanceOk(true); - } - else { - CurrencyPair cp(currentQRN_.security); - baseProduct_ = cp.NumCurrency(); - product_ = cp.ContraCurrency(currentQRN_.product); - - const auto assetType = assetManager_->GetAssetTypeForSecurity(currentQRN_.security); - if (assetType == bs::network::Asset::Type::Undefined) { - logger_->error("[RFQDealerReply::reset] could not get asset type for {}", currentQRN_.security); - } - const auto priceDecimals = UiUtils::GetPricePrecisionForAssetType(assetType); - ui_->spinBoxBidPx->setDecimals(priceDecimals); - ui_->spinBoxOfferPx->setDecimals(priceDecimals); - ui_->spinBoxBidPx->setSingleStep(std::pow(10, -priceDecimals)); - ui_->spinBoxOfferPx->setSingleStep(std::pow(10, -priceDecimals)); - - const auto priceWidget = getActivePriceWidget(); - ui_->spinBoxBidPx->setEnabled(priceWidget == ui_->spinBoxBidPx); - ui_->spinBoxOfferPx->setEnabled(priceWidget == ui_->spinBoxOfferPx); -/* priceWidget->setFocus(); - priceWidget->selectAll();*/ - - ui_->labelProductGroup->setText(tr(bs::network::Asset::toString(currentQRN_.assetType))); - ui_->labelSecurity->setText(QString::fromStdString(currentQRN_.security)); - ui_->labelReqProduct->setText(QString::fromStdString(currentQRN_.product)); - ui_->labelReqSide->setText(tr(bs::network::Side::toString(currentQRN_.side))); - ui_->labelReqQty->setText(UiUtils::displayAmountForProduct(currentQRN_.quantity - , QString::fromStdString(currentQRN_.product), currentQRN_.assetType)); - - ui_->labelRespProduct->setText(QString::fromStdString(product_)); - ui_->labelQuantity->setText(UiUtils::displayAmountForProduct(currentQRN_.quantity - , QString::fromStdString(currentQRN_.product), currentQRN_.assetType)); - ui_->labelProduct->setText(QString::fromStdString(currentQRN_.product)); - - if (sentNotifs_.count(currentQRN_.quoteRequestId) == 0) { - selectedXbtInputs_.clear(); - } - else { - const auto* lastSettlement = getLastUTXOReplyCb_(currentQRN_.settlementId); - if (lastSettlement && selectedXbtInputs_ != *lastSettlement) { - selectedXbtInputs_ = *lastSettlement; - } - } - } - - updateRespQuantity(); - updateSpinboxes(); - refreshSettlementDetails(); -} - -void RFQDealerReply::quoteReqNotifStatusChanged(const bs::network::QuoteReqNotification &qrn) -{ - if (!QuoteProvider::isRepliableStatus(qrn.status)) { - sentNotifs_.erase(qrn.quoteRequestId); - addresses_.erase(qrn.quoteRequestId); - } - - if (qrn.quoteRequestId == currentQRN_.quoteRequestId) { - updateQuoteReqNotification(qrn); - } - - refreshSettlementDetails(); -} - -void RFQDealerReply::setQuoteReqNotification(const bs::network::QuoteReqNotification &qrn, double indicBid, double indicAsk) -{ - indicBid_ = indicBid; - indicAsk_ = indicAsk; - - updateQuoteReqNotification(qrn); -} - -void RFQDealerReply::updateQuoteReqNotification(const bs::network::QuoteReqNotification &qrn) -{ - const auto &oldReqId = currentQRN_.quoteRequestId; - const bool qrnChanged = (oldReqId != qrn.quoteRequestId); - currentQRN_ = qrn; - - const bool isXBT = (qrn.assetType == bs::network::Asset::SpotXBT); - const bool isPrivMkt = (qrn.assetType == bs::network::Asset::PrivateMarket); - - dealerSellXBT_ = (isXBT || isPrivMkt) && ((qrn.product == bs::network::XbtCurrency) != (qrn.side == bs::network::Side::Sell)); - - ui_->authenticationAddressLabel->setVisible(isXBT); - ui_->authenticationAddressComboBox->setVisible(isXBT); - ui_->widgetWallet->setVisible(isXBT || isPrivMkt); - ui_->toolButtonXBTInputsSend->setVisible(dealerSellXBT_ && isXBT); - ui_->labelWallet->setText(dealerSellXBT_ ? tr("Payment Wallet") : tr("Receiving Wallet")); - - updateUiWalletFor(qrn); - - if (qrnChanged) { - reset(); - } - - if (qrn.assetType == bs::network::Asset::SpotFX || - qrn.assetType == bs::network::Asset::Undefined) { - ui_->groupBoxSettlementInputs->hide(); - } else { - ui_->groupBoxSettlementInputs->show(); - } - - updateSubmitButton(); -} - -std::shared_ptr RFQDealerReply::getCCWallet(const std::string &cc) const -{ - return walletsManager_->getCCWallet(cc); -} - -std::shared_ptr RFQDealerReply::getCCWallet(const bs::network::QuoteReqNotification &qrn) const -{ - return getCCWallet(qrn.product); -} - -void RFQDealerReply::getAddress(const std::string "eRequestId, const std::shared_ptr &wallet - , AddressType type, std::function cb) -{ - if (!wallet) { - cb({}); - return; - } - - auto address = addresses_[quoteRequestId][wallet->walletId()].at(static_cast(type)); - if (!address.empty()) { - cb(address); - return; - } - - auto cbWrap = [this, quoteRequestId, wallet, cb = std::move(cb), type](const bs::Address &addr) { - if (wallet->type() != bs::core::wallet::Type::ColorCoin && type == AddressType::Recv) { - wallet->setAddressComment(addr, bs::sync::wallet::Comment::toString(bs::sync::wallet::Comment::SettlementPayOut)); - } - addresses_[quoteRequestId][wallet->walletId()][static_cast(type)] = addr; - cb(addr); - }; - switch (type) { - case AddressType::Recv: - // BST-2474: All addresses related to trading, not just change addresses, should use internal addresses. - // CC wallets have only external addresses so getNewIntAddress will return external address instead but that's not a problem. - wallet->getNewIntAddress(cbWrap); - break; - case AddressType::Change: - wallet->getNewChangeAddress(cbWrap); - break; - } -} - -void RFQDealerReply::updateUiWalletFor(const bs::network::QuoteReqNotification &qrn) -{ - if (armory_->state() != ArmoryState::Ready) { - return; - } - if (qrn.assetType == bs::network::Asset::PrivateMarket) { - if (qrn.side == bs::network::Side::Sell) { - const auto &ccWallet = getCCWallet(qrn.product); - if (!ccWallet) { - if (signingContainer_ && !signingContainer_->isOffline()) { - MessageBoxCCWalletQuestion qryCCWallet(QString::fromStdString(qrn.product), this); - - if (qryCCWallet.exec() == QDialog::Accepted) { - if (!walletsManager_->CreateCCLeaf(qrn.product)) { - BSMessageBox errorMessage(BSMessageBox::critical, tr("Internal error") - , tr("Failed create CC subwallet.") - , this); - errorMessage.exec(); - } - } - } else { - BSMessageBox errorMessage(BSMessageBox::critical, tr("Signer not connected") - , tr("Could not create CC subwallet.") - , this); - errorMessage.exec(); - } - } - } - - updateWalletsList((qrn.side == bs::network::Side::Sell) ? UiUtils::WalletsTypes::Full : UiUtils::WalletsTypes::All); - } else if (qrn.assetType == bs::network::Asset::SpotXBT) { - updateWalletsList((qrn.side == bs::network::Side::Sell) ? (UiUtils::WalletsTypes::Full | UiUtils::WalletsTypes::HardwareSW) : UiUtils::WalletsTypes::All); - } -} - -void RFQDealerReply::priceChanged() -{ - updateRespQuantity(); - updateSubmitButton(); -} - -void RFQDealerReply::onAuthAddrChanged(int index) -{ - auto addressString = ui_->authenticationAddressComboBox->itemText(index).toStdString(); - if (addressString.empty()) { - return; - } - authAddr_ = bs::Address::fromAddressString(addressString); - authKey_.clear(); - - if (authAddr_.empty()) { - return; - } - const auto settlLeaf = walletsManager_->getSettlementLeaf(authAddr_); - - const auto &cbPubKey = [this](const SecureBinaryData &pubKey) { - authKey_ = pubKey.toHexStr(); - QMetaObject::invokeMethod(this, &RFQDealerReply::updateSubmitButton); - }; - - if (settlLeaf) { - settlLeaf->getRootPubkey(cbPubKey); - } else { - walletsManager_->createSettlementLeaf(authAddr_, cbPubKey); - } -} - -void RFQDealerReply::updateSubmitButton() -{ - if (!currentQRN_.empty() && activeQuoteSubmits_.find(currentQRN_.quoteRequestId) != activeQuoteSubmits_.end()) { - // Do not allow re-enter into submitReply as it could cause problems - ui_->pushButtonSubmit->setEnabled(false); - ui_->pushButtonPull->setEnabled(false); - return; - } - - updateBalanceLabel(); - bool isQRNRepliable = (!currentQRN_.empty() && QuoteProvider::isRepliableStatus(currentQRN_.status)); - if ((currentQRN_.assetType != bs::network::Asset::SpotFX) - && (!signingContainer_ || signingContainer_->isOffline())) { - isQRNRepliable = false; - } - - ui_->pushButtonSubmit->setEnabled(isQRNRepliable); - ui_->pushButtonPull->setEnabled(isQRNRepliable); - if (!isQRNRepliable) { - ui_->spinBoxBidPx->setEnabled(false); - ui_->spinBoxOfferPx->setEnabled(false); - return; - } - - const auto itQN = sentNotifs_.find(currentQRN_.quoteRequestId); - ui_->pushButtonPull->setEnabled(itQN != sentNotifs_.end()); - - const auto price = getPrice(); - if (qFuzzyIsNull(price) || ((itQN != sentNotifs_.end()) && qFuzzyCompare(itQN->second, price))) { - ui_->pushButtonSubmit->setEnabled(false); - return; - } - - if ((currentQRN_.assetType == bs::network::Asset::SpotXBT) && authKey_.empty()) { - ui_->pushButtonSubmit->setEnabled(false); - return; - } - - if (!assetManager_) { - ui_->pushButtonSubmit->setEnabled(false); - return; - } - - const bool isBalanceOk = checkBalance(); - ui_->pushButtonSubmit->setEnabled(isBalanceOk); - setBalanceOk(isBalanceOk); -} - -void RFQDealerReply::setBalanceOk(bool ok) -{ - if (!ok) { - QPalette palette = ui_->labelRespQty->palette(); - palette.setColor(QPalette::WindowText, Qt::red); - ui_->labelRespQty->setPalette(palette); - return; - } - ui_->labelRespQty->setPalette(QPalette()); -} - -bool RFQDealerReply::checkBalance() const -{ - if (!assetManager_) { - return false; - } - - // #UTXO_MANAGER: Balance check should account for fee? - - if ((currentQRN_.side == bs::network::Side::Buy) != (product_ == baseProduct_)) { - const auto amount = getAmount(); - if (currentQRN_.assetType == bs::network::Asset::SpotXBT) { - return amount <= getXbtBalance(bs::UTXOReservationManager::kIncludeZcDealerSpotXbt).GetValueBitcoin(); - } else if (currentQRN_.assetType == bs::network::Asset::PrivateMarket) { - return amount <= getPrivateMarketCoinBalance(); - } - const auto product = (product_ == baseProduct_) ? product_ : currentQRN_.product; - return assetManager_->checkBalance(product, amount, false); - } else if ((currentQRN_.side == bs::network::Side::Buy) && (product_ == baseProduct_)) { - const bool includeZc = - (currentQRN_.assetType == bs::network::Asset::SpotXBT) - ? bs::UTXOReservationManager::kIncludeZcDealerSpotXbt - : bs::UTXOReservationManager::kIncludeZcDealerCc; - return assetManager_->checkBalance(currentQRN_.product, currentQRN_.quantity, includeZc); - } - - if (currentQRN_.assetType == bs::network::Asset::PrivateMarket) { - return currentQRN_.quantity * getPrice() <= getXbtBalance(bs::UTXOReservationManager::kIncludeZcDealerCc).GetValueBitcoin(); - } - - const double value = getValue(); - if (qFuzzyIsNull(value)) { - return true; - } - const bool isXbt = (currentQRN_.assetType == bs::network::Asset::PrivateMarket) || - ((currentQRN_.assetType == bs::network::Asset::SpotXBT) && (product_ == baseProduct_)); - if (isXbt) { - return value <= getXbtBalance(bs::UTXOReservationManager::kIncludeZcDealerSpotXbt).GetValueBitcoin(); - } - return assetManager_->checkBalance(product_, value, false); -} - -void RFQDealerReply::walletSelected(int index) -{ - reset(); - updateSubmitButton(); -} - -QDoubleSpinBox *RFQDealerReply::getActivePriceWidget() const -{ - if (currentQRN_.empty()) { - return nullptr; - } - - if (baseProduct_ == currentQRN_.product) { - if (currentQRN_.side == bs::network::Side::Buy) { - return ui_->spinBoxOfferPx; - } - return ui_->spinBoxBidPx; - } - else { - if (currentQRN_.side == bs::network::Side::Buy) { - return ui_->spinBoxBidPx; - } - return ui_->spinBoxOfferPx; - } -} - -double RFQDealerReply::getPrice() const -{ - const auto spinBox = getActivePriceWidget(); - return spinBox ? spinBox->value() : 0; -} - -double RFQDealerReply::getValue() const -{ - const double price = getPrice(); - if (baseProduct_ == product_) { - if (!qFuzzyIsNull(price)) { - return currentQRN_.quantity / price; - } - return 0; - } - return price * currentQRN_.quantity; -} - -double RFQDealerReply::getAmount() const -{ - if (baseProduct_ == product_) { - const double price = getPrice(); - if (!qFuzzyIsNull(price)) { - return currentQRN_.quantity / price; - } - return 0; - } - return currentQRN_.quantity; -} - -std::shared_ptr RFQDealerReply::getSelectedXbtWallet(ReplyType replyType) const -{ - if (!walletsManager_) { - return nullptr; - } - if (replyType == ReplyType::Script) { - return walletsManager_->getPrimaryWallet(); - } - return walletsManager_->getHDWalletById(ui_->comboBoxXbtWallet->currentData(UiUtils::WalletIdRole).toString().toStdString()); -} - -bs::Address RFQDealerReply::selectedAuthAddress(ReplyType replyType) const -{ - if (replyType == ReplyType::Script) { - authAddressManager_->getDefault(); - } - return authAddr_; -} - -std::vector RFQDealerReply::selectedXbtInputs(ReplyType replyType) const -{ - if (replyType == ReplyType::Script) { - return {}; - } - - return selectedXbtInputs_; -} - -void RFQDealerReply::setSubmitQuoteNotifCb(RFQDealerReply::SubmitQuoteNotifCb cb) -{ - submitQuoteNotifCb_ = std::move(cb); -} - -void RFQDealerReply::setResetCurrentReservation(RFQDealerReply::ResetCurrentReservationCb cb) -{ - resetCurrentReservationCb_ = std::move(cb); -} - -void bs::ui::RFQDealerReply::setGetLastSettlementReply(GetLastUTXOReplyCb cb) -{ - getLastUTXOReplyCb_ = std::move(cb); -} - -void bs::ui::RFQDealerReply::onParentAboutToHide() -{ - if (sentNotifs_.count(currentQRN_.quoteRequestId) == 0) { - selectedXbtInputs_.clear(); - } - selectedXbtRes_.release(); -} - -void RFQDealerReply::submitReply(const bs::network::QuoteReqNotification &qrn, double price, ReplyType replyType) -{ - if (qFuzzyIsNull(price)) { - SPDLOG_LOGGER_ERROR(logger_, "invalid price"); - return; - } - - const auto itQN = sentNotifs_.find(qrn.quoteRequestId); - if (itQN != sentNotifs_.end() && itQN->second == price) { - SPDLOG_LOGGER_ERROR(logger_, "quote have been already sent"); - return; - } - - auto replyData = std::make_shared(); - replyData->qn = bs::network::QuoteNotification(qrn, authKey_, price, ""); - - if (qrn.assetType != bs::network::Asset::SpotFX) { - replyData->xbtWallet = getSelectedXbtWallet(replyType); - if (!replyData->xbtWallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't submit CC/XBT reply without XBT wallet"); - return; - } - - if (!replyData->xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXbtWallet); - replyData->walletPurpose.reset(new bs::hd::Purpose(purpose)); - } - } - - if (qrn.assetType == bs::network::Asset::SpotXBT) { - replyData->authAddr = selectedAuthAddress(replyType); - if (!replyData->authAddr.isValid()) { - SPDLOG_LOGGER_ERROR(logger_, "can't submit XBT without valid auth address"); - return; - } - - auto minXbtAmount = bs::tradeutils::minXbtAmount(utxoReservationManager_->feeRatePb()); - auto xbtAmount = XBTAmount(qrn.product == bs::network::XbtCurrency ? qrn.quantity : qrn.quantity / price); - if (xbtAmount.GetValue() < minXbtAmount.GetValue()) { - SPDLOG_LOGGER_ERROR(logger_, "XBT amount is too low to cover network fee: {}, min. amount: {}" - , xbtAmount.GetValue(), minXbtAmount.GetValue()); - return; - } - } - - auto it = activeQuoteSubmits_.find(replyData->qn.quoteRequestId); - if (it != activeQuoteSubmits_.end()) { - SPDLOG_LOGGER_ERROR(logger_, "quote submit already active for quote request '{}'", replyData->qn.quoteRequestId); - return; - } - activeQuoteSubmits_.insert(replyData->qn.quoteRequestId); - updateSubmitButton(); - - switch (qrn.assetType) { - case bs::network::Asset::SpotFX: { - submit(price, replyData); - break; - } - - case bs::network::Asset::SpotXBT: { - reserveBestUtxoSetAndSubmit(qrn.quantity, price, replyData, replyType); - break; - } - - case bs::network::Asset::PrivateMarket: { - auto ccWallet = getCCWallet(qrn); - if (!ccWallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find required CC wallet ({})", qrn.product); - return; - } - - const bool isSpendCC = qrn.side == bs::network::Side::Buy; - uint64_t spendVal; - if (isSpendCC) { - spendVal = static_cast(qrn.quantity * assetManager_->getCCLotSize(qrn.product)); - } else { - spendVal = bs::XBTAmount(price * qrn.quantity).GetValue(); - } - - auto xbtLeaves = replyData->xbtWallet->getGroup(bs::sync::hd::Wallet::getXBTGroupType())->getLeaves(); - if (xbtLeaves.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "empty XBT leaves in wallet {}", replyData->xbtWallet->walletId()); - return; - } - auto xbtWallets = std::vector>(xbtLeaves.begin(), xbtLeaves.end()); - auto xbtWallet = xbtWallets.front(); - - const auto &spendWallet = isSpendCC ? ccWallet : xbtWallet; - const auto &recvWallet = isSpendCC ? xbtWallet : ccWallet; - - preparingCCRequest_.insert(replyData->qn.quoteRequestId); - auto recvAddrCb = [this, replyData, qrn, spendWallet, spendVal, isSpendCC, ccWallet, xbtWallets, price](const bs::Address &addr) { - replyData->qn.receiptAddress = addr.display(); - replyData->qn.reqAuthKey = qrn.requestorRecvAddress; - - const auto &cbFee = [this, qrn, spendVal, spendWallet, isSpendCC, replyData, ccWallet, xbtWallets, price](float feePerByteArmory) { - auto feePerByte = std::max(feePerByteArmory, utxoReservationManager_->feeRatePb()); - auto inputsCb = [this, qrn, feePerByte, replyData, spendVal, spendWallet, isSpendCC, price] - (const std::map &inputs) - { - QMetaObject::invokeMethod(this, [this, feePerByte, qrn, replyData, spendVal, spendWallet, isSpendCC, inputs, price] { - const auto &cbChangeAddr = [this, feePerByte, qrn, replyData, spendVal, spendWallet, inputs, price, isSpendCC] - (const bs::Address &changeAddress) - { - try { - //group 1 for cc, group 2 for xbt - unsigned spendGroup = isSpendCC ? RECIP_GROUP_SPEND_1 : RECIP_GROUP_SPEND_2; - unsigned changGroup = isSpendCC ? RECIP_GROUP_CHANG_1 : RECIP_GROUP_CHANG_2; - - std::map>> recipientMap; - const auto recipient = bs::Address::fromAddressString(qrn.requestorRecvAddress).getRecipient(bs::XBTAmount{ spendVal }); - std::vector> recVec({recipient}); - recipientMap.emplace(spendGroup, std::move(recVec)); - - - Codec_SignerState::SignerState state; - state.ParseFromString(BinaryData::CreateFromHex(qrn.requestorAuthPublicKey).toBinStr()); - auto txReq = bs::sync::WalletsManager::createPartialTXRequest(spendVal, inputs - , changeAddress - , isSpendCC ? 0 : feePerByte, armory_->topBlock() - , recipientMap, changGroup, state - , false, UINT32_MAX, logger_); - logger_->debug("[RFQDealerReply::submitReply] {} input[s], fpb={}, recip={}, " - "change amount={}, prevPart={}", inputs.size(), feePerByte - , bs::Address::fromAddressString(qrn.requestorRecvAddress).display() - , txReq.change.value, qrn.requestorAuthPublicKey); - - signingContainer_->resolvePublicSpenders(txReq, [replyData, this, price, txReq] - (bs::error::ErrorCode result, const Codec_SignerState::SignerState &state) - { - if (preparingCCRequest_.count(replyData->qn.quoteRequestId) == 0) { - return; - } - - if (result == bs::error::ErrorCode::NoError) { - replyData->qn.transactionData = BinaryData::fromString(state.SerializeAsString()).toHexStr(); - replyData->utxoRes = utxoReservationManager_->makeNewReservation( - txReq.getInputs(nullptr), replyData->qn.quoteRequestId); - submit(price, replyData); - } - else { - SPDLOG_LOGGER_ERROR(logger_, "error resolving public spenders: {}" - , bs::error::ErrorCodeToString(result).toStdString()); - } - preparingCCRequest_.erase(replyData->qn.quoteRequestId); - }); - } catch (const std::exception &e) { - SPDLOG_LOGGER_ERROR(logger_, "error creating own unsigned half: {}", e.what()); - return; - } - }; - - if (isSpendCC) { - uint64_t inputsVal = 0; - for (const auto &input : inputs) { - inputsVal += input.first.getValue(); - } - if (inputsVal == spendVal) { - cbChangeAddr({}); - return; - } - } - getAddress(qrn.quoteRequestId, spendWallet, AddressType::Change, cbChangeAddr); - }); - }; - - // Try to reset current reservation if needed when user sends another quote - resetCurrentReservationCb_(replyData); - - if (isSpendCC) { - const auto inputsWrapCb = [inputsCb, ccWallet](const std::vector &utxos) { - std::map inputs; - for (const auto &utxo : utxos) { - inputs[utxo] = ccWallet->walletId(); - } - inputsCb(inputs); - }; - // For CC search for exact amount (preferable without change) - ccWallet->getSpendableTxOutList(inputsWrapCb, spendVal, true); - } else { - // For XBT request all available inputs as we don't know fee yet (createPartialTXRequest will use correct inputs if fee rate is set) - std::vector utxos; - if (!replyData->xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXbtWallet); - utxos = utxoReservationManager_->getAvailableXbtUTXOs( - replyData->xbtWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcDealerCc); - } - else { - utxos = utxoReservationManager_->getAvailableXbtUTXOs( - replyData->xbtWallet->walletId(), bs::UTXOReservationManager::kIncludeZcDealerCc); - } - auto fixedUtxo = utxoReservationManager_->convertUtxoToPartialFixedInput(replyData->xbtWallet->walletId(), utxos); - inputsCb(fixedUtxo.inputs); - } - }; - - if (qrn.side == bs::network::Side::Buy) { - cbFee(0); - } else { - walletsManager_->estimatedFeePerByte(2, cbFee, this); - } - }; - // recv. address is always set automatically - getAddress(qrn.quoteRequestId, recvWallet, AddressType::Recv, recvAddrCb); - break; - } - - default: { - break; - } - } -} - -void RFQDealerReply::updateWalletsList(int walletsFlags) -{ - auto oldWalletId = UiUtils::getSelectedWalletId(ui_->comboBoxXbtWallet); - auto oldType = UiUtils::getSelectedWalletType(ui_->comboBoxXbtWallet); - int defaultIndex = UiUtils::fillHDWalletsComboBox(ui_->comboBoxXbtWallet, walletsManager_, walletsFlags); - int oldIndex = UiUtils::selectWalletInCombobox(ui_->comboBoxXbtWallet, oldWalletId, oldType); - if (oldIndex < 0) { - ui_->comboBoxXbtWallet->setCurrentIndex(defaultIndex); - } - walletSelected(ui_->comboBoxXbtWallet->currentIndex()); -} - -bool RFQDealerReply::isXbtSpend() const -{ - bool isXbtSpend = (currentQRN_.assetType == bs::network::Asset::PrivateMarket && currentQRN_.side == bs::network::Side::Sell) || - ((currentQRN_.assetType == bs::network::Asset::SpotXBT) && (currentQRN_.side == bs::network::Side::Buy)); - return isXbtSpend; -} - -void RFQDealerReply::onReservedUtxosChanged(const std::string &walletId, const std::vector &utxos) -{ - onTransactionDataChanged(); -} - -void RFQDealerReply::submitButtonClicked() -{ - const auto price = getPrice(); - if (!ui_->pushButtonSubmit->isEnabled() || price == 0) { - return; - } - - submitReply(currentQRN_, price, ReplyType::Manual); - - updateSubmitButton(); -} - -void RFQDealerReply::pullButtonClicked() -{ - if (currentQRN_.empty()) { - return; - } - emit pullQuoteNotif(currentQRN_.settlementId, currentQRN_.quoteRequestId, currentQRN_.sessionToken); - sentNotifs_.erase(currentQRN_.quoteRequestId); - updateSubmitButton(); - refreshSettlementDetails(); -} - -bool RFQDealerReply::eventFilter(QObject *watched, QEvent *evt) -{ - if (evt->type() == QEvent::KeyPress) { - auto keyID = static_cast(evt)->key(); - if ((keyID == Qt::Key_Return) || (keyID == Qt::Key_Enter)) { - submitButtonClicked(); - } - } else if ((evt->type() == QEvent::FocusIn) || (evt->type() == QEvent::FocusOut)) { - auto activePriceWidget = getActivePriceWidget(); - if (activePriceWidget != nullptr) { - autoUpdatePrices_ = !(activePriceWidget->hasFocus()); - } else { - autoUpdatePrices_ = false; - } - } - return QWidget::eventFilter(watched, evt); -} - -void RFQDealerReply::showCoinControl() -{ - auto xbtWallet = getSelectedXbtWallet(ReplyType::Manual); - if (!xbtWallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find XBT wallet"); - return; - } - - const auto &leaves = xbtWallet->getGroup(xbtWallet->getXBTGroupType())->getLeaves(); - std::vector> wallets(leaves.begin(), leaves.end()); - ui_->toolButtonXBTInputsSend->setEnabled(false); - - // Need to release current reservation to be able select them back - selectedXbtRes_.release(); - std::vector allUTXOs; - if (!xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXbtWallet); - allUTXOs = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcDealerSpotXbt); - } - else { - allUTXOs = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet->walletId(), bs::UTXOReservationManager::kIncludeZcDealerSpotXbt); - } - - ui_->toolButtonXBTInputsSend->setEnabled(true); - - const bool useAutoSel = selectedXbtInputs_.empty(); - - auto inputs = std::make_shared(allUTXOs); - - // Set this to false is needed otherwise current selection would be cleared - inputs->SetUseAutoSel(useAutoSel); - for (const auto &utxo : selectedXbtInputs_) { - inputs->SetUTXOSelection(utxo.getTxHash(), utxo.getTxOutIndex()); - } - - CoinControlDialog dialog(inputs, true, this); - int rc = dialog.exec(); - if (rc != QDialog::Accepted) { - return; - } - - auto selectedInputs = dialog.selectedInputs(); - if (bs::UtxoReservation::instance()->containsReservedUTXO(selectedInputs)) { - BSMessageBox(BSMessageBox::critical, tr("UTXO reservation failed"), - tr("Some of selected UTXOs has been already reserved"), this).exec(); - showCoinControl(); - return; - } - - selectedXbtInputs_.clear(); - for (const auto &selectedInput : selectedInputs) { - selectedXbtInputs_.push_back(selectedInput); - } - - if (!selectedXbtInputs_.empty()) { - selectedXbtRes_ = utxoReservationManager_->makeNewReservation(selectedInputs); - } - - updateSubmitButton(); -} - -void RFQDealerReply::validateGUI() -{ - updateSubmitButton(); -} - -void RFQDealerReply::onTransactionDataChanged() -{ - QMetaObject::invokeMethod(this, &RFQDealerReply::updateSubmitButton); -} - -void RFQDealerReply::onMDUpdate(bs::network::Asset::Type, const QString &security, bs::network::MDFields mdFields) -{ - auto &mdInfo = mdInfo_[security.toStdString()]; - mdInfo.merge(bs::network::MDField::get(mdFields)); - if (autoUpdatePrices_ && (currentQRN_.security == security.toStdString()) - && (bestQPrices_.find(currentQRN_.quoteRequestId) == bestQPrices_.end())) { - if (!qFuzzyIsNull(mdInfo.bidPrice)) { - ui_->spinBoxBidPx->setValue(mdInfo.bidPrice); - } - if (!qFuzzyIsNull(mdInfo.askPrice)) { - ui_->spinBoxOfferPx->setValue(mdInfo.askPrice); - } - } -} - -void RFQDealerReply::onBestQuotePrice(const QString reqId, double price, bool own) -{ - bestQPrices_[reqId.toStdString()] = price; - - if (!currentQRN_.empty() && (currentQRN_.quoteRequestId == reqId.toStdString())) { - if (autoUpdatePrices_) { - auto priceWidget = getActivePriceWidget(); - if (priceWidget && !own) { - double improvedPrice = price; - const auto assetType = assetManager_->GetAssetTypeForSecurity(currentQRN_.security); - if (assetType != bs::network::Asset::Type::Undefined) { - const auto pip = std::pow(10, -UiUtils::GetPricePrecisionForAssetType(assetType)); - if (priceWidget == ui_->spinBoxBidPx) { - improvedPrice += pip; - } else { - improvedPrice -= pip; - } - } else { - logger_->error("[RFQDealerReply::onBestQuotePrice] could not get type for {}", currentQRN_.security); - } - priceWidget->setValue(improvedPrice); - } - } - } - - updateSpinboxes(); -} - -void RFQDealerReply::onAQReply(const bs::network::QuoteReqNotification &qrn, double price) -{ - // Check assets first - bool ok = true; - if (qrn.assetType == bs::network::Asset::Type::SpotXBT) { - if (qrn.side == bs::network::Side::Sell && qrn.product == bs::network::XbtCurrency) { - CurrencyPair currencyPair(qrn.security); - ok = assetManager_->checkBalance(currencyPair.ContraCurrency(qrn.product), qrn.quantity * price, bs::UTXOReservationManager::kIncludeZcDealerSpotXbt); - } - else if (qrn.side == bs::network::Side::Buy && qrn.product != bs::network::XbtCurrency) { - ok = assetManager_->checkBalance(qrn.product, qrn.quantity, bs::UTXOReservationManager::kIncludeZcDealerSpotXbt); - } - } - else if (qrn.assetType == bs::network::Asset::Type::SpotFX) { - if (qrn.side == bs::network::Side::Sell) { - auto quantity = qrn.quantity; - CurrencyPair currencyPair(qrn.security); - const auto contrCurrency = currencyPair.ContraCurrency(qrn.product); - if (currencyPair.NumCurrency() == contrCurrency) { - quantity /= price; - } - else { - quantity *= price; - } - ok = assetManager_->checkBalance(currencyPair.ContraCurrency(qrn.product), quantity, false); - } - else { - ok = assetManager_->checkBalance(qrn.product, qrn.quantity, false); - } - } - - if (!ok) { - return; - } - - submitReply(qrn, price, ReplyType::Script); -} - -void RFQDealerReply::onHDLeafCreated(const std::string& ccName) -{ - if (product_ != ccName) { - return; - } - - auto ccLeaf = walletsManager_->getCCWallet(ccName); - if (ccLeaf == nullptr) { - logger_->error("[RFQDealerReply::onHDLeafCreated] CC wallet {} should exists" - , ccName); - return; - } - - updateUiWalletFor(currentQRN_); - reset(); -} - -void RFQDealerReply::onCreateHDWalletError(const std::string& ccName, bs::error::ErrorCode result) -{ - if (product_ != ccName) { - return; - } - - BSMessageBox(BSMessageBox::critical, tr("Failed to create wallet") - , tr("Failed to create wallet") - , tr("%1 Wallet. Error: %2").arg(QString::fromStdString(product_)).arg(bs::error::ErrorCodeToString(result))).exec(); -} - -void RFQDealerReply::onCelerConnected() -{ - celerConnected_ = true; - validateGUI(); -} - -void RFQDealerReply::onCelerDisconnected() -{ - logger_->info("Disabled auto-quoting due to Celer disconnection"); - celerConnected_ = false; - validateGUI(); -} - -void RFQDealerReply::onAutoSignStateChanged() -{ - if (autoSignProvider_->autoSignState() == bs::error::ErrorCode::NoError) { - ui_->comboBoxXbtWallet->setCurrentText(autoSignProvider_->getAutoSignWalletName()); - } - ui_->comboBoxXbtWallet->setEnabled(autoSignProvider_->autoSignState() == bs::error::ErrorCode::AutoSignDisabled); -} - -void bs::ui::RFQDealerReply::onQuoteCancelled(const std::string "eId) -{ - preparingCCRequest_.erase(quoteId); -} - -void bs::ui::RFQDealerReply::onUTXOReservationChanged(const std::string& walletId) -{ - if (walletId.empty()) { - updateBalanceLabel(); - return; - } - - auto xbtWallet = getSelectedXbtWallet(ReplyType::Manual); - if (xbtWallet && (walletId == xbtWallet->walletId() || xbtWallet->getLeaf(walletId))) { - updateBalanceLabel(); - } -} - -void bs::ui::RFQDealerReply::submit(double price, const std::shared_ptr& replyData) -{ - SPDLOG_LOGGER_DEBUG(logger_, "submitted quote reply on {}: {}/{}", replyData->qn.quoteRequestId, replyData->qn.bidPx, replyData->qn.offerPx); - sentNotifs_[replyData->qn.quoteRequestId] = price; - submitQuoteNotifCb_(replyData); - activeQuoteSubmits_.erase(replyData->qn.quoteRequestId); - updateSubmitButton(); - refreshSettlementDetails(); -} - -void bs::ui::RFQDealerReply::reserveBestUtxoSetAndSubmit(double quantity, double price, - const std::shared_ptr& replyData, ReplyType replyType) -{ - auto replyRFQWrapper = [rfqReply = QPointer(this), - price, replyData, replyType] (std::vector utxos) { - if (!rfqReply) { - return; - } - - if (utxos.empty()) { - if (replyType == ReplyType::Manual) { - replyData->fixedXbtInputs = rfqReply->selectedXbtInputs_; - replyData->utxoRes = std::move(rfqReply->selectedXbtRes_); - } - - rfqReply->submit(price, replyData); - return; - } - - if (replyType == ReplyType::Manual) { - rfqReply->selectedXbtInputs_ = utxos; - } - - replyData->utxoRes = rfqReply->utxoReservationManager_->makeNewReservation(utxos); - replyData->fixedXbtInputs = std::move(utxos); - - rfqReply->submit(price, replyData); - }; - - if ((replyData->qn.side == bs::network::Side::Sell && replyData->qn.product != bs::network::XbtCurrency) || - (replyData->qn.side == bs::network::Side::Buy && replyData->qn.product == bs::network::XbtCurrency)) { - replyRFQWrapper({}); - return; // Nothing to reserve - } - - // We shouldn't recalculate better utxo set if that not first quote response - // otherwise, we should chose best set if that wasn't done by user and this is not auto quoting script - if (sentNotifs_.count(replyData->qn.quoteRequestId) || (!selectedXbtInputs_.empty() && replyType == ReplyType::Manual)) { - replyRFQWrapper({}); - return; // already reserved by user - } - - auto security = mdInfo_.find(replyData->qn.security); - if (security == mdInfo_.end()) { - // there is no MD data available so we really can't forecast - replyRFQWrapper({}); - return; - } - - BTCNumericTypes::satoshi_type xbtQuantity = 0; - if (replyData->qn.side == bs::network::Side::Buy) { - if (replyData->qn.assetType == bs::network::Asset::PrivateMarket) { - xbtQuantity = XBTAmount(quantity * mdInfo_[replyData->qn.security].bidPrice).GetValue(); - } - else if (replyData->qn.assetType == bs::network::Asset::SpotXBT) { - xbtQuantity = XBTAmount(quantity / mdInfo_[replyData->qn.security].askPrice).GetValue(); - } - } - else { - xbtQuantity = XBTAmount(quantity).GetValue(); - } - xbtQuantity = static_cast(xbtQuantity * tradeutils::reservationQuantityMultiplier()); - - auto cbBestUtxoSet = [rfqReply = QPointer(this), - replyRFQ = std::move(replyRFQWrapper)](std::vector&& utxos) { - if (!rfqReply) { - return; - } - - replyRFQ(std::move(utxos)); - }; - - // Check amount (required for AQ scripts) - auto checkAmount = bs::UTXOReservationManager::CheckAmount::Enabled; - - if (!replyData->xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXbtWallet); - utxoReservationManager_->getBestXbtUtxoSet(replyData->xbtWallet->walletId(), purpose, - xbtQuantity, cbBestUtxoSet, true, checkAmount); - } - else { - const bool includeZc = - (replyData->qn.assetType == bs::network::Asset::SpotXBT) - ? bs::UTXOReservationManager::kIncludeZcDealerSpotXbt - : bs::UTXOReservationManager::kIncludeZcDealerCc; - utxoReservationManager_->getBestXbtUtxoSet(replyData->xbtWallet->walletId(), - xbtQuantity, cbBestUtxoSet, true, checkAmount, includeZc); - } - - -} - -void bs::ui::RFQDealerReply::refreshSettlementDetails() -{ - if (currentQRN_.empty()) { - ui_->groupBoxSettlementInputs->setEnabled(true); - return; - } - - ui_->groupBoxSettlementInputs->setEnabled(!sentNotifs_.count(currentQRN_.quoteRequestId)); -} - -void bs::ui::RFQDealerReply::updateSpinboxes() -{ - auto setSpinboxValue = [&](CustomDoubleSpinBox* spinBox, double value, double changeSign) { - if (qFuzzyIsNull(value)) { - spinBox->clear(); - return; - } - - if (!spinBox->isEnabled()) { - spinBox->setValue(value); - return; - } - - auto bestQuotePrice = bestQPrices_.find(currentQRN_.quoteRequestId); - if (bestQuotePrice != bestQPrices_.end()) { - spinBox->setValue(bestQuotePrice->second + changeSign * spinBox->singleStep()); - } - else { - spinBox->setValue(value); - } - }; - - // The best quote response for buy orders should decrease price - setSpinboxValue(ui_->spinBoxBidPx, indicBid_, 1.0); - setSpinboxValue(ui_->spinBoxOfferPx, indicAsk_, -1.0); -} - -void bs::ui::RFQDealerReply::updateBalanceLabel() -{ - QString totalBalance = kNoBalanceAvailable; - - const bool includeZc = - (currentQRN_.assetType == bs::network::Asset::SpotXBT) - ? bs::UTXOReservationManager::kIncludeZcDealerSpotXbt - : bs::UTXOReservationManager::kIncludeZcDealerCc; - - if (isXbtSpend()) { - totalBalance = tr("%1 %2") - .arg(UiUtils::displayAmount(getXbtBalance(includeZc).GetValueBitcoin())) - .arg(QString::fromStdString(bs::network::XbtCurrency)); - } else if ((currentQRN_.side == bs::network::Side::Buy) && (currentQRN_.assetType == bs::network::Asset::PrivateMarket)) { - totalBalance = tr("%1 %2") - .arg(UiUtils::displayCCAmount(getPrivateMarketCoinBalance())) - .arg(QString::fromStdString(baseProduct_)); - } else { - if (assetManager_) { - totalBalance = tr("%1 %2") - .arg(UiUtils::displayCurrencyAmount(assetManager_->getBalance(product_, includeZc, nullptr))) - .arg(QString::fromStdString(currentQRN_.side == bs::network::Side::Buy ? baseProduct_ : product_)); - } - } - - ui_->labelBalanceValue->setText(totalBalance); - ui_->cashBalanceLabel->setText(selectedXbtInputs_.empty() ? kAvailableBalance : kReservedBalance); -} - -bs::XBTAmount RFQDealerReply::getXbtBalance(bool includeZc) const -{ - const auto fixedInputs = selectedXbtInputs(ReplyType::Manual); - if (!fixedInputs.empty()) { - uint64_t sum = 0; - for (const auto &utxo : fixedInputs) { - sum += utxo.getValue(); - } - return bs::XBTAmount(sum); - } - - auto xbtWallet = getSelectedXbtWallet(ReplyType::Manual); - if (!xbtWallet) { - return {}; - } - - if (!xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXbtWallet); - return bs::XBTAmount(utxoReservationManager_->getAvailableXbtUtxoSum( - xbtWallet->walletId(), purpose, includeZc)); - } - else { - return bs::XBTAmount(utxoReservationManager_->getAvailableXbtUtxoSum( - xbtWallet->walletId(), includeZc)); - } -} - -BTCNumericTypes::balance_type bs::ui::RFQDealerReply::getPrivateMarketCoinBalance() const -{ - auto ccWallet = getCCWallet(currentQRN_.product); - if (!ccWallet) { - return 0; - } - return ccWallet->getSpendableBalance(); -} diff --git a/BlockSettleUILib/Trading/RFQDealerReply.h b/BlockSettleUILib/Trading/RFQDealerReply.h deleted file mode 100644 index 391633717..000000000 --- a/BlockSettleUILib/Trading/RFQDealerReply.h +++ /dev/null @@ -1,255 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __RFQ_DEALER_REPLY_H__ -#define __RFQ_DEALER_REPLY_H__ - -#include -#include - -#include -#include -#include -#include -#include - -#include "BSErrorCode.h" -#include "CommonTypes.h" -#include "EncryptionUtils.h" -#include "QWalletInfo.h" -#include "HDPath.h" -#include "UtxoReservationToken.h" -#include "CommonTypes.h" - -namespace Ui { - class RFQDealerReply; -} -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - namespace hd { - class Leaf; - } - class Wallet; - class WalletsManager; - } - class UTXOReservationManager; -} -class ApplicationSettings; -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class AutoSignScriptProvider; -class QuoteProvider; -class SelectedTransactionInputs; -class SignContainer; -class CustomDoubleSpinBox; -class MarketDataProvider; -class ConnectionManager; - -QT_BEGIN_NAMESPACE -class QDoubleSpinBox; -class QPushButton; -QT_END_NAMESPACE - -namespace UiUtils { - enum WalletsTypes : int; -} - -namespace bs { - namespace network { - struct QuoteNotification; - } - - namespace ui { - - struct SubmitQuoteReplyData - { - bs::network::QuoteNotification qn; - bs::UtxoReservationToken utxoRes; - std::shared_ptr xbtWallet; - bs::Address authAddr; - std::vector fixedXbtInputs; - std::unique_ptr walletPurpose; - }; - - class RFQDealerReply : public QWidget - { - Q_OBJECT - - public: - RFQDealerReply(QWidget* parent = nullptr); - ~RFQDealerReply() override; - - void init(const std::shared_ptr logger - , const std::shared_ptr & - , const std::shared_ptr& assetManager - , const std::shared_ptr& quoteProvider - , const std::shared_ptr & - , const std::shared_ptr &connectionManager - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &autoSignQuoteProvider - , const std::shared_ptr& utxoReservationManager); - - void setWalletsManager(const std::shared_ptr &); - - CustomDoubleSpinBox* bidSpinBox() const; - CustomDoubleSpinBox* offerSpinBox() const; - - QPushButton* pullButton() const; - QPushButton* quoteButton() const; - - using SubmitQuoteNotifCb = std::function &data)>; - void setSubmitQuoteNotifCb(SubmitQuoteNotifCb cb); - - using ResetCurrentReservationCb = std::function &data)>; - void setResetCurrentReservation(ResetCurrentReservationCb cb); - - using GetLastUTXOReplyCb = std::function*(const std::string&)>; - void setGetLastSettlementReply(GetLastUTXOReplyCb cb); - - void onParentAboutToHide(); - - signals: - void pullQuoteNotif(const std::string& settlementId, const std::string& reqId, const std::string& reqSessToken); - - public slots: - void setQuoteReqNotification(const network::QuoteReqNotification &, double indicBid, double indicAsk); - void quoteReqNotifStatusChanged(const network::QuoteReqNotification &); - void onMDUpdate(bs::network::Asset::Type, const QString &security, bs::network::MDFields); - void onBestQuotePrice(const QString reqId, double price, bool own); - void onCelerConnected(); - void onCelerDisconnected(); - void onAutoSignStateChanged(); - void onQuoteCancelled(const std::string "eId); - - private slots: - void initUi(); - void priceChanged(); - void updateSubmitButton(); - void submitButtonClicked(); - void pullButtonClicked(); - void showCoinControl(); - void walletSelected(int index); - void onTransactionDataChanged(); - void onAQReply(const bs::network::QuoteReqNotification &qrn, double price); - void onReservedUtxosChanged(const std::string &walletId, const std::vector &); - void onHDLeafCreated(const std::string& ccName); - void onCreateHDWalletError(const std::string& ccName, bs::error::ErrorCode result); - void onAuthAddrChanged(int); - void onUTXOReservationChanged(const std::string& walletId); - - protected: - bool eventFilter(QObject *watched, QEvent *evt) override; - - private: - std::unique_ptr ui_; - std::shared_ptr logger_; - std::shared_ptr walletsManager_; - std::shared_ptr authAddressManager_; - std::shared_ptr assetManager_; - std::shared_ptr quoteProvider_; - std::shared_ptr appSettings_; - std::shared_ptr connectionManager_; - std::shared_ptr signingContainer_; - std::shared_ptr armory_; - std::shared_ptr autoSignProvider_; - std::shared_ptr utxoReservationManager_; - std::string authKey_; - bs::Address authAddr_; - - std::unordered_map sentNotifs_; - network::QuoteReqNotification currentQRN_; - unsigned int payInRecipId_{UINT_MAX}; - bool dealerSellXBT_{false}; - - double indicBid_{}; - double indicAsk_{}; - std::atomic_bool autoUpdatePrices_{true}; - - std::string autoSignWalletId_; - - std::string product_; - std::string baseProduct_; - - bool celerConnected_{false}; - - std::unordered_map bestQPrices_; - QFont invalidBalanceFont_; - - std::unordered_map mdInfo_; - - std::vector selectedXbtInputs_; - bs::UtxoReservationToken selectedXbtRes_; - - SubmitQuoteNotifCb submitQuoteNotifCb_; - ResetCurrentReservationCb resetCurrentReservationCb_; - GetLastUTXOReplyCb getLastUTXOReplyCb_; - - std::set preparingCCRequest_; - - private: - enum class ReplyType - { - Manual, - Script, - }; - - enum class AddressType - { - Recv, - Change, - - Max = Change, - }; - - void reset(); - void validateGUI(); - void updateRespQuantity(); - void updateSpinboxes(); - void updateQuoteReqNotification(const network::QuoteReqNotification &); - void updateBalanceLabel(); - double getPrice() const; - double getValue() const; - double getAmount() const; - std::shared_ptr getCCWallet(const std::string &cc) const; - std::shared_ptr getCCWallet(const bs::network::QuoteReqNotification &qrn) const; - void getAddress(const std::string "eRequestId, const std::shared_ptr &wallet - , AddressType type, std::function cb); - void setBalanceOk(bool ok); - bool checkBalance() const; - XBTAmount getXbtBalance(bool includeZc) const; - BTCNumericTypes::balance_type getPrivateMarketCoinBalance() const; - QDoubleSpinBox *getActivePriceWidget() const; - void updateUiWalletFor(const bs::network::QuoteReqNotification &qrn); - // xbtWallet - what XBT wallet to use for XBT/CC trades (selected from UI for manual trades, default wallet for AQ trades), empty for FX trades - void submitReply(const network::QuoteReqNotification &qrn, double price, ReplyType replyType); - void updateWalletsList(int walletsFlags); - bool isXbtSpend() const; - std::shared_ptr getSelectedXbtWallet(ReplyType replyType) const; - bs::Address selectedAuthAddress(ReplyType replyType) const; - std::vector selectedXbtInputs(ReplyType replyType) const; - void submit(double price, const std::shared_ptr& replyData); - void reserveBestUtxoSetAndSubmit(double quantity, double price, - const std::shared_ptr& replyData, ReplyType replyType); - void refreshSettlementDetails(); - - - std::set activeQuoteSubmits_; - std::map(AddressType::Max) + 1>>> addresses_; - }; - - } //namespace ui -} //namespace bs - -#endif // __RFQ_TICKET_XBT_H__ diff --git a/BlockSettleUILib/Trading/RFQDealerReply.ui b/BlockSettleUILib/Trading/RFQDealerReply.ui deleted file mode 100644 index dc19cec24..000000000 --- a/BlockSettleUILib/Trading/RFQDealerReply.ui +++ /dev/null @@ -1,1665 +0,0 @@ - - - - RFQDealerReply - - - - 0 - 0 - 738 - 750 - - - - - 0 - 0 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 10 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - RECEIVED RFQ - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Product Group - - - true - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - - 75 - true - - - - QFrame::Raised - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - false - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Security - - - true - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - - 75 - true - - - - QFrame::Raised - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Product - - - true - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - - 75 - true - - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - false - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Side - - - true - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - - 75 - true - - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - false - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Quantity - - - true - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - - 75 - true - - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - false - - - true - - - - - - - - - - - - - QUOTE RESPONSE - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 6 - - - 9 - - - 9 - - - 9 - - - 9 - - - - - - 80 - 0 - - - - - 80 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 0 - 20 - - - - - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - Product - - - - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - Quantity - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 10 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 3 - - - QLayout::SetMinimumSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Requested - - - 5 - - - true - - - - - - - - 0 - 0 - - - - - 0 - - - QLayout::SetMinimumSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - -- - - - 5 - - - false - - - true - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - -- - - - 0 - - - 5 - - - false - - - true - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - QLayout::SetMinimumSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Responsive - - - 5 - - - true - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - -- - - - 5 - - - false - - - true - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - -- - - - 0 - - - 5 - - - false - - - true - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 9 - - - 9 - - - 9 - - - 9 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 50 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Bid - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - 0 - - - 10000000.000000000000000 - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Offer - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - 0 - - - 10000000.000000000000000 - - - - - - - - - - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 3 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - Balance - - - - - - - font-weight: bold; - - - xxx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - SETTLEMENT DETAILS - - - true - - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Payment Wallet - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 21 - - - - - 16777215 - 21 - - - - - - - - - 100 - 21 - - - - - 100 - 21 - - - - &Inputs - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Authentication Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 74 - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - false - - - - 0 - 40 - - - - - 16777215 - 40 - - - - - - - PULL - - - - - - - - 0 - 40 - - - - - 16777215 - 40 - - - - - - - QUOTE - - - - - - - - - - - CustomDoubleSpinBox - QDoubleSpinBox -
CustomControls/CustomDoubleSpinBox.h
-
-
- - -
diff --git a/BlockSettleUILib/Trading/RFQDialog.cpp b/BlockSettleUILib/Trading/RFQDialog.cpp deleted file mode 100644 index 6ccf57617..000000000 --- a/BlockSettleUILib/Trading/RFQDialog.cpp +++ /dev/null @@ -1,409 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RFQDialog.h" -#include "ui_RFQDialog.h" - -#include - -#include "AssetManager.h" -#include "BSMessageBox.h" -#include "QuoteProvider.h" -#include "RFQRequestWidget.h" -#include "ReqCCSettlementContainer.h" -#include "ReqXBTSettlementContainer.h" -#include "RfqStorage.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" -#include "WalletSignerContainer.h" -#include "Wallets/SyncHDWallet.h" - - -RFQDialog::RFQDialog(const std::shared_ptr &logger - , const std::string &id, const bs::network::RFQ& rfq - , const std::shared_ptr& quoteProvider - , const std::shared_ptr& authAddressManager - , const std::shared_ptr& assetManager - , const std::shared_ptr &walletsManager - , const std::shared_ptr &signContainer - , const std::shared_ptr &armory - , const std::shared_ptr &celerClient - , const std::shared_ptr &appSettings - , const std::shared_ptr &rfqStorage - , const std::shared_ptr &xbtWallet - , const bs::Address &recvXbtAddrIfSet - , const bs::Address &authAddr - , const std::shared_ptr &utxoReservationManager - , const std::map &fixedXbtInputs - , bs::UtxoReservationToken fixedXbtUtxoRes - , bs::UtxoReservationToken ccUtxoRes - , std::unique_ptr purpose - , RFQRequestWidget *parent) - : QDialog(parent) - , ui_(new Ui::RFQDialog()) - , logger_(logger) - , id_(id), rfq_(rfq) - , recvXbtAddrIfSet_(recvXbtAddrIfSet) - , quoteProvider_(quoteProvider) - , authAddressManager_(authAddressManager) - , walletsManager_(walletsManager) - , signContainer_(signContainer) - , assetMgr_(assetManager) - , armory_(armory) - , celerClient_(celerClient) - , appSettings_(appSettings) - , rfqStorage_(rfqStorage) - , xbtWallet_(xbtWallet) - , authAddr_(authAddr) - , fixedXbtInputs_(fixedXbtInputs) - , fixedXbtUtxoRes_(std::move(fixedXbtUtxoRes)) - , requestWidget_(parent) - , utxoReservationManager_(utxoReservationManager) - , ccUtxoRes_(std::move(ccUtxoRes)) - , walletPurpose_(std::move(purpose)) -{ - ui_->setupUi(this); - - ui_->pageRequestingQuote->SetAssetManager(assetMgr_); - ui_->pageRequestingQuote->SetCelerClient(celerClient_); - - // NOTE: RFQDialog could be destroyed before SettlementContainer work is done. - // Do not make connections that must live after RFQDialog closing. - - connect(ui_->pageRequestingQuote, &RequestingQuoteWidget::cancelRFQ, this, &RFQDialog::reject); - connect(ui_->pageRequestingQuote, &RequestingQuoteWidget::requestTimedOut, this, &RFQDialog::onTimeout); - connect(ui_->pageRequestingQuote, &RequestingQuoteWidget::quoteAccepted, this, &RFQDialog::onRFQResponseAccepted, Qt::QueuedConnection); - connect(ui_->pageRequestingQuote, &RequestingQuoteWidget::quoteFinished, this, &RFQDialog::onQuoteFinished); - connect(ui_->pageRequestingQuote, &RequestingQuoteWidget::quoteFailed, this, &RFQDialog::onQuoteFailed); - - connect(quoteProvider_.get(), &QuoteProvider::quoteReceived, this, &RFQDialog::onQuoteReceived); - connect(quoteProvider_.get(), &QuoteProvider::quoteRejected, ui_->pageRequestingQuote, &RequestingQuoteWidget::onReject); - connect(quoteProvider_.get(), &QuoteProvider::orderRejected, ui_->pageRequestingQuote, &RequestingQuoteWidget::onReject); - connect(quoteProvider_.get(), &QuoteProvider::quoteCancelled, ui_->pageRequestingQuote, &RequestingQuoteWidget::onQuoteCancelled); - - connect(quoteProvider_.get(), &QuoteProvider::orderFailed, this, &RFQDialog::onOrderFailed); - connect(quoteProvider_.get(), &QuoteProvider::quoteOrderFilled, this, &RFQDialog::onOrderFilled); - connect(quoteProvider_.get(), &QuoteProvider::signTxRequested, this, &RFQDialog::onSignTxRequested); - - ui_->pageRequestingQuote->populateDetails(rfq_); - - quoteProvider_->SubmitRFQ(rfq_); -} - -RFQDialog::~RFQDialog() = default; - -void RFQDialog::onOrderFilled(const std::string "eId) -{ - if (quote_.quoteId != quoteId) { - return; - } - - if (rfq_.assetType == bs::network::Asset::SpotFX) { - ui_->pageRequestingQuote->onOrderFilled(quoteId); - } -} - -void RFQDialog::onOrderFailed(const std::string& quoteId, const std::string& reason) -{ - if (quote_.quoteId != quoteId) { - return; - } - - if (rfq_.assetType == bs::network::Asset::SpotFX) { - ui_->pageRequestingQuote->onOrderFailed(quoteId, reason); - } - close(); -} - -void RFQDialog::onRFQResponseAccepted(const QString &reqId, const bs::network::Quote "e) -{ - emit accepted(id_); - quote_ = quote; - - if (rfq_.assetType == bs::network::Asset::SpotFX) { - quoteProvider_->AcceptQuoteFX(reqId, quote); - } - else { - if (rfq_.assetType == bs::network::Asset::SpotXBT) { - curContainer_ = newXBTcontainer(); - } else { - curContainer_ = newCCcontainer(); - } - - if (curContainer_) { - rfqStorage_->addSettlementContainer(curContainer_); - curContainer_->activate(); - - // Do not capture `this` here! - auto failedCb = [qId = quote_.quoteId, curContainer = curContainer_.get()] - (const std::string& quoteId, const std::string& reason) - { - if (qId == quoteId) { - curContainer->cancel(); - } - }; - connect(quoteProvider_.get(), &QuoteProvider::orderFailed, curContainer_.get(), failedCb); - } - } -} - -void RFQDialog::logError(const std::string &id, bs::error::ErrorCode code, const QString &errorMessage) -{ - logger_->error("[RFQDialog::logError] {}: {}", id, errorMessage.toStdString()); - - if (bs::error::ErrorCode::TxCancelled != code) { - // Do not use this as the parent as it will be destroyed when RFQDialog is closed - QMetaObject::invokeMethod(qApp, [code, errorMessage] { - MessageBoxBroadcastError(errorMessage, code).exec(); - }, Qt::QueuedConnection); - } -} - -std::shared_ptr RFQDialog::newXBTcontainer() -{ - if (!xbtWallet_) { - SPDLOG_LOGGER_ERROR(logger_, "xbt wallet is not set"); - return nullptr; - } - - const bool expandTxInfo = appSettings_->get( - ApplicationSettings::DetailedSettlementTxDialogByDefault); - - const auto tier1XbtLimit = appSettings_->get( - ApplicationSettings::SubmittedAddressXbtLimit); - - try { - xbtSettlContainer_ = std::make_shared(logger_ - , authAddressManager_, signContainer_, armory_, xbtWallet_, walletsManager_ - , rfq_, quote_, authAddr_, fixedXbtInputs_, std::move(fixedXbtUtxoRes_), utxoReservationManager_ - , std::move(walletPurpose_), recvXbtAddrIfSet_, expandTxInfo, tier1XbtLimit); - - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::settlementAccepted - , this, &RFQDialog::onXBTSettlementAccepted); - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::settlementCancelled - , this, &QDialog::close); - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::acceptQuote - , this, &RFQDialog::onXBTQuoteAccept); - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::error - , this, &RFQDialog::logError); - - // Use requestWidget_ as RFQDialog could be already destroyed before this moment - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::sendUnsignedPayinToPB - , requestWidget_, &RFQRequestWidget::sendUnsignedPayinToPB); - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::sendSignedPayinToPB - , requestWidget_, &RFQRequestWidget::sendSignedPayinToPB); - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::sendSignedPayoutToPB - , requestWidget_, &RFQRequestWidget::sendSignedPayoutToPB); - - connect(xbtSettlContainer_.get(), &ReqXBTSettlementContainer::cancelTrade - , requestWidget_, &RFQRequestWidget::cancelXBTTrade); - } - catch (const std::exception &e) { - logError({}, bs::error::ErrorCode::InternalError - , tr("Failed to create XBT settlement container: %1") - .arg(QString::fromLatin1(e.what()))); - } - - return xbtSettlContainer_; -} - -void RFQDialog::hideIfNoRemoteSignerMode() -{ - if (signContainer_->opMode() != SignContainer::OpMode::Remote) { - hide(); - } -} - -std::shared_ptr RFQDialog::newCCcontainer() -{ - const bool expandTxInfo = appSettings_->get( - ApplicationSettings::DetailedSettlementTxDialogByDefault); - - try { - ccSettlContainer_ = std::make_shared(logger_ - , signContainer_, armory_, assetMgr_, walletsManager_, rfq_, quote_, xbtWallet_, - fixedXbtInputs_, utxoReservationManager_, std::move(walletPurpose_), std::move(ccUtxoRes_), expandTxInfo); - - connect(ccSettlContainer_.get(), &ReqCCSettlementContainer::txSigned - , this, &RFQDialog::onCCTxSigned); - connect(ccSettlContainer_.get(), &ReqCCSettlementContainer::sendOrder - , this, &RFQDialog::onCCQuoteAccepted); - connect(ccSettlContainer_.get(), &ReqCCSettlementContainer::settlementCancelled - , this, &QDialog::close); - connect(ccSettlContainer_.get(), &ReqCCSettlementContainer::error - , this, &RFQDialog::logError); - - connect(ccSettlContainer_.get(), &ReqCCSettlementContainer::cancelTrade - , requestWidget_, &RFQRequestWidget::cancelCCTrade); - - // Do not make circular dependency, capture bare pointer - auto orderUpdatedCb = [qId = quote_.quoteId, ccContainer = ccSettlContainer_.get()] (const bs::network::Order& order) { - if (order.status == bs::network::Order::Pending && order.quoteId == qId) { - ccContainer->setClOrdId(order.clOrderId); - } - }; - connect(quoteProvider_.get(), &QuoteProvider::orderUpdated, ccSettlContainer_.get(), orderUpdatedCb); - } - catch (const std::exception &e) { - logError({}, bs::error::ErrorCode::InternalError - , tr("Failed to create CC settlement container: %1") - .arg(QString::fromLatin1(e.what()))); - } - - return ccSettlContainer_; -} - -void RFQDialog::onCCTxSigned() -{ - quoteProvider_->SignTxRequest(ccOrderId_, ccSettlContainer_->txSignedData()); - close(); -} - -void RFQDialog::reject() -{ - cancel(false); - emit cancelled(id_); - QDialog::reject(); -} - -void RFQDialog::cancel(bool force) -{ - // curContainer_->cancel call could emit settlementCancelled which will result in RFQDialog::reject re-enter. - // This will result in duplicated finished signals. Let's add a workaround for this. - if (isRejectStarted_) { - return; - } - isRejectStarted_ = true; - - if (cancelOnClose_) { - if (curContainer_) { - if (curContainer_->cancel()) { - logger_->debug("[RFQDialog::reject] container cancelled"); - } else { - logger_->warn("[RFQDialog::reject] settlement container failed to cancel"); - } - } - else { - fixedXbtUtxoRes_.release(); - ccUtxoRes_.release(); - } - } - - if (cancelOnClose_) { - quoteProvider_->CancelQuote(QString::fromStdString(rfq_.requestId)); - } - if (force) { - close(); - } -} - -void RFQDialog::onTimeout() -{ - emit expired(id_); - cancelOnClose_ = false; - hide(); -} - -void RFQDialog::onQuoteFinished() -{ - emit accepted(id_); - cancelOnClose_ = false; - hide(); -} - -void RFQDialog::onQuoteFailed() -{ - emit cancelled(id_); - close(); -} - -bool RFQDialog::close() -{ - curContainer_.reset(); - cancelOnClose_ = false; - return QDialog::close(); -} - -void RFQDialog::onQuoteReceived(const bs::network::Quote& quote) -{ - ui_->pageRequestingQuote->onQuoteReceived(quote); -} - -void RFQDialog::onXBTSettlementAccepted() -{ - if (xbtSettlContainer_) { - close(); - } else { - logger_->error("[RFQDialog::onXBTSettlementAccepted] XBT settlement accepted with empty container"); - } -} - -void RFQDialog::onCCQuoteAccepted() -{ - if (ccSettlContainer_) { - quoteProvider_->AcceptQuote(QString::fromStdString(rfq_.requestId), quote_ - , ccSettlContainer_->txData()); - } -} - -void RFQDialog::onSignTxRequested(QString orderId, QString reqId, QDateTime timestamp) -{ - if (QString::fromStdString(rfq_.requestId) != reqId) { - logger_->debug("[RFQDialog::onSignTxRequested] not our request. ignore"); - return; - } - - if (ccSettlContainer_ == nullptr) { - logger_->error("[RFQDialog::onSignTxRequested] could not sign with missing container"); - return; - } - - hideIfNoRemoteSignerMode(); - - ccOrderId_ = orderId; - ccSettlContainer_->startSigning(timestamp); -} - -void RFQDialog::onXBTQuoteAccept(std::string reqId, std::string hexPayoutTx) -{ - quoteProvider_->AcceptQuote(QString::fromStdString(reqId), quote_, hexPayoutTx); -} - -void RFQDialog::onUnsignedPayinRequested(const std::string& settlementId) -{ - if (!xbtSettlContainer_ || (settlementId != quote_.settlementId)) { - return; - } - - xbtSettlContainer_->onUnsignedPayinRequested(settlementId); -} - -void RFQDialog::onSignedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash, QDateTime timestamp) -{ - if (!xbtSettlContainer_ || (settlementId != quote_.settlementId)) { - return; - } - - hideIfNoRemoteSignerMode(); - - xbtSettlContainer_->onSignedPayoutRequested(settlementId, payinHash, timestamp); -} - -void RFQDialog::onSignedPayinRequested(const std::string& settlementId - , const BinaryData& unsignedPayin, const BinaryData &payinHash, QDateTime timestamp) -{ - if (!xbtSettlContainer_ || (settlementId != quote_.settlementId)) { - return; - } - - hideIfNoRemoteSignerMode(); - - xbtSettlContainer_->onSignedPayinRequested(settlementId, payinHash, timestamp); -} diff --git a/BlockSettleUILib/Trading/RFQDialog.h b/BlockSettleUILib/Trading/RFQDialog.h deleted file mode 100644 index f1664d50f..000000000 --- a/BlockSettleUILib/Trading/RFQDialog.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __RFQ_DIALOG_H__ -#define __RFQ_DIALOG_H__ - -#include - -#include - -#include "CommonTypes.h" -#include "UtxoReservationToken.h" -#include "BSErrorCode.h" -#include "HDPath.h" - -namespace Ui { - class RFQDialog; -} -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - namespace hd { - class Wallet; - } - class Wallet; - class WalletsManager; - } - class SettlementContainer; - class UTXOReservationManager; -} -class ApplicationSettings; -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class BaseCelerClient; -class CCSettlementTransactionWidget; -class QuoteProvider; -class RFQRequestWidget; -class ReqCCSettlementContainer; -class ReqXBTSettlementContainer; -class RfqStorage; -class WalletSignerContainer; -class XBTSettlementTransactionWidget; - -class RFQDialog : public QDialog -{ -Q_OBJECT - -public: - RFQDialog(const std::shared_ptr &logger - , const std::string &id, const bs::network::RFQ& rfq - , const std::shared_ptr& quoteProvider - , const std::shared_ptr& authAddressManager - , const std::shared_ptr& assetManager - , const std::shared_ptr &walletsManager - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &celerClient - , const std::shared_ptr &appSettings - , const std::shared_ptr &rfqStorage - , const std::shared_ptr &xbtWallet - , const bs::Address &recvXbtAddrIfSet - , const bs::Address &authAddr - , const std::shared_ptr &utxoReservationManager - , const std::map &fixedXbtInputs - , bs::UtxoReservationToken fixedXbtUtxoRes - , bs::UtxoReservationToken ccUtxoRes - , std::unique_ptr purpose - , RFQRequestWidget* parent = nullptr); - ~RFQDialog() override; - - void cancel(bool force = true); - -signals: - void accepted(const std::string &id); - void expired(const std::string &id); - void cancelled(const std::string &id); - -protected: - void reject() override; - -public slots: - void onUnsignedPayinRequested(const std::string& settlementId); - void onSignedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash, QDateTime timestamp); - void onSignedPayinRequested(const std::string& settlementId, const BinaryData& unsignedPayin - , const BinaryData &payinHash, QDateTime timestamp); - -private slots: - bool close(); - void onTimeout(); - void onQuoteFinished(); - void onQuoteFailed(); - - void onRFQResponseAccepted(const QString &reqId, const bs::network::Quote& quote); - void onQuoteReceived(const bs::network::Quote& quote); - void onOrderFilled(const std::string "eId); - void onOrderFailed(const std::string& quoteId, const std::string& reason); - void onXBTSettlementAccepted(); - - void onSignTxRequested(QString orderId, QString reqId, QDateTime timestamp); - void onCCQuoteAccepted(); - void onCCTxSigned(); - - void onXBTQuoteAccept(std::string reqId, std::string hexPayoutTx); - void logError(const std::string &id, bs::error::ErrorCode code - , const QString &errorMessage); - -private: - std::shared_ptr newCCcontainer(); - std::shared_ptr newXBTcontainer(); - void hideIfNoRemoteSignerMode(); - -private: - std::unique_ptr ui_; - std::shared_ptr logger_; - const std::string id_; - const bs::network::RFQ rfq_; - bs::network::Quote quote_; - bs::Address recvXbtAddrIfSet_; - - std::shared_ptr quoteProvider_; - std::shared_ptr authAddressManager_; - std::shared_ptr walletsManager_; - std::shared_ptr signContainer_; - std::shared_ptr assetMgr_; - std::shared_ptr armory_; - std::shared_ptr celerClient_; - std::shared_ptr appSettings_; - std::shared_ptr rfqStorage_; - std::shared_ptr xbtWallet_; - std::shared_ptr utxoReservationManager_; - - std::shared_ptr curContainer_; - std::shared_ptr ccSettlContainer_; - std::shared_ptr xbtSettlContainer_; - - const bs::Address authAddr_; - const std::map fixedXbtInputs_; - bs::UtxoReservationToken fixedXbtUtxoRes_; - - bool cancelOnClose_ = true; - bool isRejectStarted_ = false; - - RFQRequestWidget *requestWidget_{}; - - QString ccOrderId_; - bs::UtxoReservationToken ccUtxoRes_; - std::unique_ptr walletPurpose_; - -}; - -#endif // __RFQ_DIALOG_H__ diff --git a/BlockSettleUILib/Trading/RFQDialog.ui b/BlockSettleUILib/Trading/RFQDialog.ui deleted file mode 100644 index 719c5425c..000000000 --- a/BlockSettleUILib/Trading/RFQDialog.ui +++ /dev/null @@ -1,64 +0,0 @@ - - - - RFQDialog - - - - 0 - 0 - 340 - 246 - - - - - 0 - 0 - - - - Request-For-Quote - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - RequestingQuoteWidget - QWidget -
RequestingQuoteWidget.h
- 1 -
-
- - -
diff --git a/BlockSettleUILib/Trading/RFQReplyWidget.cpp b/BlockSettleUILib/Trading/RFQReplyWidget.cpp deleted file mode 100644 index 575a5b86e..000000000 --- a/BlockSettleUILib/Trading/RFQReplyWidget.cpp +++ /dev/null @@ -1,705 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RFQReplyWidget.h" -#include "ui_RFQReplyWidget.h" - -#include "AssetManager.h" -#include "AuthAddressManager.h" -#include "AutoSignQuoteProvider.h" -#include "BSMessageBox.h" -#include "CelerClient.h" -#include "CelerSubmitQuoteNotifSequence.h" -#include "CustomControls/CustomDoubleSpinBox.h" -#include "DealerCCSettlementContainer.h" -#include "DealerXBTSettlementContainer.h" -#include "DialogManager.h" -#include "MDCallbacksQt.h" -#include "OrderListModel.h" -#include "OrdersView.h" -#include "QuoteProvider.h" -#include "RFQBlotterTreeView.h" -#include "SelectedTransactionInputs.h" -#include "WalletSignerContainer.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "UserScriptRunner.h" -#include "UtxoReservationManager.h" - -#include "bs_proxy_terminal_pb.pb.h" - -#include - -#include -#include - -using namespace bs::ui; -using namespace Blocksettle::Communication; - -enum class DealingPages : int -{ - ShieldPage = 0, - DealingPage -}; - -RFQReplyWidget::RFQReplyWidget(QWidget* parent) - : TabWithShortcut(parent) - , ui_(new Ui::RFQReplyWidget()) -{ - ui_->setupUi(this); - ui_->shieldPage->setTabType(QLatin1String("dealing")); - - connect(ui_->widgetQuoteRequests, &QuoteRequestsWidget::quoteReqNotifStatusChanged, ui_->pageRFQReply - , &RFQDealerReply::quoteReqNotifStatusChanged, Qt::QueuedConnection); - connect(ui_->shieldPage, &RFQShieldPage::requestPrimaryWalletCreation, - this, &RFQReplyWidget::requestPrimaryWalletCreation); - - - ui_->shieldPage->showShieldLoginToResponseRequired(); - popShield(); -} - -RFQReplyWidget::~RFQReplyWidget() = default; - -void RFQReplyWidget::setWalletsManager(const std::shared_ptr &walletsManager) -{ - if (!walletsManager_ && walletsManager) { - walletsManager_ = walletsManager; - ui_->pageRFQReply->setWalletsManager(walletsManager_); - ui_->shieldPage->init(walletsManager_, authAddressManager_, appSettings_); - - if (signingContainer_) { - auto primaryWallet = walletsManager_->getPrimaryWallet(); - if (primaryWallet != nullptr) { - signingContainer_->GetInfo(primaryWallet->walletId()); - } - } - } -} - -void RFQReplyWidget::shortcutActivated(ShortcutType s) -{ - switch (s) { - case ShortcutType::Alt_1 : { - ui_->widgetQuoteRequests->view()->activate(); - } - break; - - case ShortcutType::Alt_2 : { - if (ui_->pageRFQReply->bidSpinBox()->isVisible()) { - if (ui_->pageRFQReply->bidSpinBox()->isEnabled()) - ui_->pageRFQReply->bidSpinBox()->setFocus(); - else - ui_->pageRFQReply->offerSpinBox()->setFocus(); - } else { - ui_->pageRFQReply->setFocus(); - } - } - break; - - case ShortcutType::Alt_3 : { - ui_->treeViewOrders->activate(); - } - break; - - case ShortcutType::Ctrl_Q : { - if (ui_->pageRFQReply->quoteButton()->isEnabled()) - ui_->pageRFQReply->quoteButton()->click(); - } - break; - - case ShortcutType::Ctrl_P : { - if (ui_->pageRFQReply->pullButton()->isEnabled()) - ui_->pageRFQReply->pullButton()->click(); - } - break; - - default : - break; - } -} - -void RFQReplyWidget::init(const std::shared_ptr &logger - , const std::shared_ptr& celerClient - , const std::shared_ptr &authAddressManager - , const std::shared_ptr& quoteProvider - , const std::shared_ptr& mdCallbacks - , const std::shared_ptr& assetManager - , const std::shared_ptr &appSettings - , const std::shared_ptr &dialogManager - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &connectionManager - , const std::shared_ptr &autoSignProvider - , const std::shared_ptr &utxoReservationManager - , OrderListModel *orderListModel -) -{ - logger_ = logger; - celerClient_ = celerClient; - authAddressManager_ = authAddressManager; - quoteProvider_ = quoteProvider; - assetManager_ = assetManager; - dialogManager_ = dialogManager; - signingContainer_ = container; - armory_ = armory; - appSettings_ = appSettings; - connectionManager_ = connectionManager; - autoSignProvider_ = autoSignProvider; - utxoReservationManager_ = utxoReservationManager; - - statsCollector_ = std::make_shared(appSettings - , ApplicationSettings::Filter_MD_QN_cnt); - - ui_->widgetQuoteRequests->init(logger_, quoteProvider_, assetManager, statsCollector_ - , appSettings, celerClient_); - ui_->pageRFQReply->init(logger, authAddressManager, assetManager, quoteProvider_ - , appSettings, connectionManager, signingContainer_, armory_, autoSignProvider - , utxoReservationManager); - ui_->widgetAutoSignQuote->init(autoSignProvider); - - connect(ui_->widgetQuoteRequests, &QuoteRequestsWidget::Selected, this - , &RFQReplyWidget::onSelected); - - ui_->pageRFQReply->setSubmitQuoteNotifCb([this] - (const std::shared_ptr &data) - { - statsCollector_->onQuoteSubmitted(data->qn); - quoteProvider_->SubmitQuoteNotif(data->qn); - ui_->widgetQuoteRequests->onQuoteReqNotifReplied(data->qn); - onReplied(data); - }); - - ui_->pageRFQReply->setGetLastSettlementReply([this] - (const std::string& settlementId) -> const std::vector* - { - auto lastReply = sentXbtReplies_.find(settlementId); - if (lastReply == sentXbtReplies_.end()) { - return nullptr; - } - - return &(lastReply->second.utxosPayinFixed); - }); - - ui_->pageRFQReply->setResetCurrentReservation([this](const std::shared_ptr &data) { - onResetCurrentReservation(data); - }); - - connect(ui_->pageRFQReply, &RFQDealerReply::pullQuoteNotif, this - , &RFQReplyWidget::onPulled); - - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, ui_->widgetQuoteRequests - , &QuoteRequestsWidget::onSecurityMDUpdated); - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, ui_->pageRFQReply - , &RFQDealerReply::onMDUpdate); - - connect(quoteProvider_.get(), &QuoteProvider::orderUpdated, this - , &RFQReplyWidget::onOrder); - connect(quoteProvider_.get(), &QuoteProvider::quoteCancelled, this - , &RFQReplyWidget::onQuoteCancelled); - connect(quoteProvider_.get(), &QuoteProvider::bestQuotePrice, ui_->widgetQuoteRequests - , &QuoteRequestsWidget::onBestQuotePrice, Qt::QueuedConnection); - connect(quoteProvider_.get(), &QuoteProvider::bestQuotePrice, ui_->pageRFQReply - , &RFQDealerReply::onBestQuotePrice, Qt::QueuedConnection); - - connect(quoteProvider_.get(), &QuoteProvider::quoteRejected, this, &RFQReplyWidget::onQuoteRejected); - - connect(quoteProvider_.get(), &QuoteProvider::quoteNotifCancelled, this - , &RFQReplyWidget::onQuoteNotifCancelled); - connect(quoteProvider_.get(), &QuoteProvider::allQuoteNotifCancelled - , ui_->widgetQuoteRequests, &QuoteRequestsWidget::onAllQuoteNotifCancelled); - connect(quoteProvider_.get(), &QuoteProvider::signTxRequested, this - , &RFQReplyWidget::onSignTxRequested); - - ui_->treeViewOrders->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - ui_->treeViewOrders->setModel(orderListModel); - ui_->treeViewOrders->initWithModel(orderListModel); - - - connect(celerClient_.get(), &BaseCelerClient::OnConnectedToServer, this - , &RFQReplyWidget::onConnectedToCeler); - connect(celerClient_.get(), &BaseCelerClient::OnConnectionClosed, this - , &RFQReplyWidget::onDisconnectedFromCeler); - - connect(ui_->widgetQuoteRequests->view(), &TreeViewWithEnterKey::enterKeyPressed - , this, &RFQReplyWidget::onEnterKeyPressed); -} - -void RFQReplyWidget::forceCheckCondition() -{ - const QModelIndex index = ui_->widgetQuoteRequests->view()->selectionModel()->currentIndex(); - if (!index.isValid()) { - return; - } - ui_->widgetQuoteRequests->onQuoteReqNotifSelected(index); -} - -void RFQReplyWidget::onReplied(const std::shared_ptr &data) -{ - switch (data->qn.assetType) { - case bs::network::Asset::SpotXBT: { - assert(data->xbtWallet); - if (sentReplyToSettlementsIds_.count(data->qn.quoteRequestId)) { - break; // already answered, nothing to do there - } - sentReplyToSettlementsIds_[data->qn.quoteRequestId] = data->qn.settlementId; - settlementToReplyIds_[data->qn.settlementId] = data->qn.quoteRequestId; - auto &reply = sentXbtReplies_[data->qn.settlementId]; - reply.xbtWallet = data->xbtWallet; - reply.authAddr = data->authAddr; - reply.utxosPayinFixed = data->fixedXbtInputs; - reply.utxoRes = std::move(data->utxoRes); - reply.walletPurpose = std::move(data->walletPurpose); - break; - } - - case bs::network::Asset::PrivateMarket: { - assert(data->xbtWallet); - auto &reply = sentCCReplies_[data->qn.quoteRequestId]; - reply.recipientAddress = data->qn.receiptAddress; - reply.requestorAuthAddress = data->qn.reqAuthKey; - reply.utxoRes = std::move(data->utxoRes); - reply.xbtWallet = data->xbtWallet; - reply.walletPurpose = std::move(data->walletPurpose); - break; - } - - default: { - break; - } - } -} - -void RFQReplyWidget::onPulled(const std::string& settlementId, const std::string& reqId, const std::string& reqSessToken) -{ - sentXbtReplies_.erase(settlementId); - sentReplyToSettlementsIds_.erase(reqId); - settlementToReplyIds_.erase(settlementId); - quoteProvider_->CancelQuoteNotif(QString::fromStdString(reqId), QString::fromStdString(reqSessToken)); -} - -void RFQReplyWidget::onUserConnected(const bs::network::UserType &) -{ - const bool autoSigning = appSettings_->get(ApplicationSettings::AutoSigning); - const bool autoQuoting = appSettings_->get(ApplicationSettings::AutoQouting); - - ui_->widgetAutoSignQuote->onUserConnected(autoSigning, autoQuoting); -} - -void RFQReplyWidget::onResetCurrentReservation(const std::shared_ptr &data) -{ - switch (data->qn.assetType) { - case bs::network::Asset::PrivateMarket: { - auto it = sentCCReplies_.find(data->qn.quoteRequestId); - if (it != sentCCReplies_.end()) { - it->second.utxoRes.release(); - } - break; - } - - default: { - break; - } - } -} - -void RFQReplyWidget::onOrder(const bs::network::Order &order) -{ - const auto "eReqId = quoteProvider_->getQuoteReqId(order.quoteId); - if (order.assetType == bs::network::Asset::SpotFX) { - if (order.status == bs::network::Order::Filled) { - onSettlementComplete(quoteReqId); - quoteProvider_->delQuoteReqId(quoteReqId); - } - return; - } - - const bool expandTxInfo = appSettings_->get( - ApplicationSettings::DetailedSettlementTxDialogByDefault); - - if (order.status == bs::network::Order::Pending) { - if (order.assetType == bs::network::Asset::PrivateMarket) { - if (quoteReqId.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "quoteReqId is empty for {}", order.quoteId); - return; - } - const auto itCCSR = sentCCReplies_.find(quoteReqId); - if (itCCSR == sentCCReplies_.end()) { - SPDLOG_LOGGER_DEBUG(logger_, "missing previous CC reply for {}", quoteReqId); - return; - } - sentReplyToSettlementsIds_[quoteReqId] = order.clOrderId; - settlementToReplyIds_[order.clOrderId] = quoteReqId; - auto &sr = itCCSR->second; - try { - const auto settlContainer = std::make_shared(logger_ - , order, quoteReqId, assetManager_->getCCLotSize(order.product) - , assetManager_->getCCGenesisAddr(order.product), sr.recipientAddress - , sr.xbtWallet, signingContainer_, armory_, walletsManager_ - , std::move(sr.walletPurpose), std::move(sr.utxoRes), expandTxInfo); - connect(settlContainer.get(), &DealerCCSettlementContainer::signTxRequest - , this, &RFQReplyWidget::saveTxData); - connect(settlContainer.get(), &DealerCCSettlementContainer::error - , this, &RFQReplyWidget::onTransactionError); - connect(settlContainer.get(), &DealerCCSettlementContainer::cancelTrade - , this, &RFQReplyWidget::onCancelCCTrade); - connect(settlContainer.get(), &DealerCCSettlementContainer::completed - , this, &RFQReplyWidget::onSettlementComplete); - - // Do not make circular dependency, capture bare pointer - auto orderUpdatedCb = [settlContainer = settlContainer.get(), quoteId = order.quoteId] - (const std::string& failedQuoteId, const std::string& reason) { - if (settlContainer && quoteId == failedQuoteId) { - settlContainer->cancel(); - } - }; - connect(quoteProvider_.get(), &QuoteProvider::orderFailed, settlContainer.get(), orderUpdatedCb); - - ui_->widgetQuoteRequests->addSettlementContainer(settlContainer); - settlContainer->activate(); - - } catch (const std::exception &e) { - BSMessageBox box(BSMessageBox::critical, tr("Settlement error") - , tr("Failed to start dealer's CC settlement") - , QString::fromLatin1(e.what()) - , this); - box.exec(); - } - } else { - const auto &it = sentXbtReplies_.find(order.settlementId); - if (it == sentXbtReplies_.end()) { - // Looks like this is not error, not sure why we need this - SPDLOG_LOGGER_DEBUG(logger_, "haven't seen QuoteNotif with settlId={}", order.settlementId); - return; - } - try { - auto &reply = it->second; - // Dealers can't select receiving address, use new - const auto recvXbtAddr = bs::Address(); - - const auto tier1XbtLimit = appSettings_->get( - ApplicationSettings::SubmittedAddressXbtLimit); - - const auto settlContainer = std::make_shared(logger_ - , order, walletsManager_, reply.xbtWallet, quoteProvider_, signingContainer_ - , armory_, authAddressManager_, reply.authAddr, reply.utxosPayinFixed - , recvXbtAddr, utxoReservationManager_, std::move(reply.walletPurpose) - , std::move(reply.utxoRes), expandTxInfo, tier1XbtLimit); - - connect(settlContainer.get(), &DealerXBTSettlementContainer::sendUnsignedPayinToPB - , this, &RFQReplyWidget::sendUnsignedPayinToPB); - connect(settlContainer.get(), &DealerXBTSettlementContainer::sendSignedPayinToPB - , this, &RFQReplyWidget::sendSignedPayinToPB); - connect(settlContainer.get(), &DealerXBTSettlementContainer::sendSignedPayoutToPB - , this, &RFQReplyWidget::sendSignedPayoutToPB); - connect(settlContainer.get(), &DealerXBTSettlementContainer::cancelTrade - , this, &RFQReplyWidget::onCancelXBTTrade); - connect(settlContainer.get(), &DealerXBTSettlementContainer::error - , this, &RFQReplyWidget::onTransactionError); - connect(settlContainer.get(), &DealerCCSettlementContainer::completed - , this, &RFQReplyWidget::onSettlementComplete); - - connect(this, &RFQReplyWidget::unsignedPayinRequested, settlContainer.get() - , &DealerXBTSettlementContainer::onUnsignedPayinRequested); - connect(this, &RFQReplyWidget::signedPayoutRequested, settlContainer.get() - , &DealerXBTSettlementContainer::onSignedPayoutRequested); - connect(this, &RFQReplyWidget::signedPayinRequested, settlContainer.get() - , &DealerXBTSettlementContainer::onSignedPayinRequested); - - // Do not make circular dependency, capture bare pointer - connect(quoteProvider_.get(), &QuoteProvider::orderFailed, settlContainer.get() - , [settlContainer = settlContainer.get(), quoteId = order.quoteId] - (const std::string& failedQuoteId, const std::string& reason) { - if (quoteId == failedQuoteId) { - settlContainer->cancel(); - } - }); - - // Add before calling activate as this will hook some events - ui_->widgetQuoteRequests->addSettlementContainer(settlContainer); - - settlContainer->activate(); - - } catch (const std::exception &e) { - SPDLOG_LOGGER_ERROR(logger_, "settlement failed: {}", e.what()); - BSMessageBox box(BSMessageBox::critical, tr("Settlement error") - , tr("Failed to start dealer's settlement") - , QString::fromLatin1(e.what()) - , this); - box.exec(); - } - } - } else { - if (!quoteReqId.empty()) { - sentCCReplies_.erase(quoteReqId); - quoteProvider_->delQuoteReqId(quoteReqId); - } - sentXbtReplies_.erase(order.settlementId); - } -} - -void RFQReplyWidget::onQuoteCancelled(const QString &reqId, bool userCancelled) -{ - eraseReply(reqId); - ui_->widgetQuoteRequests->onQuoteReqCancelled(reqId, userCancelled); - ui_->pageRFQReply->onQuoteCancelled(reqId.toStdString()); -} - -void RFQReplyWidget::onQuoteRejected(const QString &reqId, const QString &reason) -{ - eraseReply(reqId); - ui_->widgetQuoteRequests->onQuoteRejected(reqId, reason); -} - -void RFQReplyWidget::onQuoteNotifCancelled(const QString &reqId) -{ - eraseReply(reqId); - ui_->widgetQuoteRequests->onQuoteNotifCancelled(reqId); -} - -void RFQReplyWidget::onConnectedToCeler() -{ - ui_->shieldPage->showShieldSelectTargetDealing(); - popShield(); - ui_->pageRFQReply->onCelerConnected(); -} - -void RFQReplyWidget::onDisconnectedFromCeler() -{ - ui_->shieldPage->showShieldLoginToResponseRequired(); - popShield(); - ui_->pageRFQReply->onCelerDisconnected(); -} - -void RFQReplyWidget::onEnterKeyPressed(const QModelIndex &index) -{ - ui_->widgetQuoteRequests->onQuoteReqNotifSelected(index); - - if (ui_->pageRFQReply->quoteButton()->isEnabled()) { - ui_->pageRFQReply->quoteButton()->click(); - return; - } - - if (ui_->pageRFQReply->pullButton()->isEnabled()) { - ui_->pageRFQReply->pullButton()->click(); - return; - } -} - -void RFQReplyWidget::onSelected(const QString& productGroup, const bs::network::QuoteReqNotification& request, double indicBid, double indicAsk) -{ - if (!checkConditions(productGroup, request)) { - return; - } - - ui_->pageRFQReply->setQuoteReqNotification(request, indicBid, indicAsk); -} - -void RFQReplyWidget::onTransactionError(const std::string &id - , bs::error::ErrorCode code, const QString& error) -{ - const auto &itReqId = settlementToReplyIds_.find(id); - if (itReqId != settlementToReplyIds_.end()) { - ((AQScriptRunner *)autoSignProvider_->scriptRunner())->cancelled(itReqId->second); - } - if (bs::error::ErrorCode::TxCancelled != code) { - // Use QueuedConnection to not start new even loop from SettlementContainer callbacks. - // Otherwise SettlementContainer might be already destroyed when this method returns. - QMetaObject::invokeMethod(this, [this, error, code] { - MessageBoxBroadcastError(error, code, this).exec(); - }, Qt::QueuedConnection); - } -} - -void RFQReplyWidget::onCancelXBTTrade(const std::string& settlementId) -{ - const auto &itReqId = settlementToReplyIds_.find(settlementId); - if (itReqId != settlementToReplyIds_.end()) { - ((AQScriptRunner *)autoSignProvider_->scriptRunner())->cancelled(itReqId->second); - } - emit cancelXBTTrade(settlementId); -} - -void RFQReplyWidget::onCancelCCTrade(const std::string& clientOrderId) -{ - const auto &itReqId = settlementToReplyIds_.find(clientOrderId); - if (itReqId != settlementToReplyIds_.end()) { - ((AQScriptRunner *)autoSignProvider_->scriptRunner())->cancelled(itReqId->second); - } - emit cancelCCTrade(clientOrderId); -} - -void RFQReplyWidget::onSettlementComplete(const std::string &id) -{ - const auto &itReqId = settlementToReplyIds_.find(id); - if (itReqId == settlementToReplyIds_.end()) { - ((AQScriptRunner *)autoSignProvider_->scriptRunner())->settled(id); // FX settlement - } - else { - ((AQScriptRunner *)autoSignProvider_->scriptRunner())->settled(itReqId->second); - } -} - -void RFQReplyWidget::saveTxData(QString orderId, std::string txData) -{ - quoteProvider_->SignTxRequest(orderId, txData); -} - -void RFQReplyWidget::onSignTxRequested(QString orderId, QString reqId, QDateTime timestamp) -{ - Q_UNUSED(reqId); - - if (!ui_->widgetQuoteRequests->StartCCSignOnOrder(orderId, timestamp)) { - // Not an error because onSignTxRequested is also called for requesters - logger_->debug("[RFQReplyWidget::onSignTxRequested] failed to initiate sign on CC order: {}" - , orderId.toStdString()); - } -} - - -void RFQReplyWidget::showSettlementDialog(QDialog *dlg) -{ - dlg->setAttribute(Qt::WA_DeleteOnClose); - - dialogManager_->adjustDialogPosition(dlg); - - dlg->show(); -} - -bool RFQReplyWidget::checkConditions(const QString& productGroup , const bs::network::QuoteReqNotification& request) -{ - ui_->stackedWidget->setEnabled(true); - - if (productGroup.isEmpty() || request.product.empty()) { - ui_->shieldPage->showShieldSelectTargetDealing(); - popShield(); - return true; - } - - using UserType = CelerClient::CelerUserType; - const UserType userType = celerClient_->celerUserType(); - - using GroupType = RFQShieldPage::ProductType; - const GroupType group = RFQShieldPage::getProductGroup(productGroup); - - switch (userType) { - case UserType::Market: { - if (group == GroupType::SpotFX) { - ui_->shieldPage->showShieldReservedTradingParticipant(); - popShield(); - return false; - } - else if (group == GroupType::SpotXBT) { - ui_->shieldPage->showShieldReservedDealingParticipant(); - popShield(); - return false; - } - else if (ui_->shieldPage->checkWalletSettings(group, QString::fromStdString(request.product))) { - popShield(); - return false; - } - break; - } - case UserType::Trading: { - if (group == GroupType::SpotXBT) { - ui_->shieldPage->showShieldReservedDealingParticipant(); - return false; - } else if (group == GroupType::PrivateMarket && - ui_->shieldPage->checkWalletSettings(group, QString::fromStdString(request.product))) { - popShield(); - return false; - } - break; - } - case UserType::Dealing: { - if ((group == GroupType::SpotXBT || group == GroupType::PrivateMarket) && - ui_->shieldPage->checkWalletSettings(group, QString::fromStdString(request.product))) { - popShield(); - return false; - } - break; - break; - } - default: { - break; - } - } - - if (ui_->stackedWidget->currentIndex() != static_cast(DealingPages::DealingPage)) { - showEditableRFQPage(); - } - - return true; -} - -void RFQReplyWidget::popShield() -{ - ui_->stackedWidget->setEnabled(true); - - ui_->stackedWidget->setCurrentIndex(static_cast(DealingPages::ShieldPage)); - ui_->pageRFQReply->setDisabled(true); -} - -void RFQReplyWidget::showEditableRFQPage() -{ - ui_->stackedWidget->setEnabled(true); - ui_->pageRFQReply->setEnabled(true); - ui_->stackedWidget->setCurrentIndex(static_cast(DealingPages::DealingPage)); -} - - -void RFQReplyWidget::eraseReply(const QString &reqId) -{ - const auto &itSettlId = sentReplyToSettlementsIds_.find(reqId.toStdString()); - if (itSettlId != sentReplyToSettlementsIds_.end()) { - settlementToReplyIds_.erase(itSettlId->second); - sentXbtReplies_.erase(itSettlId->second); - sentReplyToSettlementsIds_.erase(itSettlId); - } - sentCCReplies_.erase(reqId.toStdString()); -} - -void RFQReplyWidget::hideEvent(QHideEvent* event) -{ - ui_->pageRFQReply->onParentAboutToHide(); - QWidget::hideEvent(event); -} - -void RFQReplyWidget::onMessageFromPB(const ProxyTerminalPb::Response &response) -{ - switch (response.data_case()) { - case Blocksettle::Communication::ProxyTerminalPb::Response::kSendUnsignedPayin: { - const auto &command = response.send_unsigned_payin(); - emit unsignedPayinRequested(command.settlement_id()); - break; - } - - case Blocksettle::Communication::ProxyTerminalPb::Response::kSignPayout: { - const auto &command = response.sign_payout(); - auto timestamp = QDateTime::fromMSecsSinceEpoch(command.timestamp_ms()); - // payin_data - payin hash . binary - emit signedPayoutRequested(command.settlement_id(), BinaryData::fromString(command.payin_data()), timestamp); - break; - } - - case Blocksettle::Communication::ProxyTerminalPb::Response::kSignPayin: { - auto command = response.sign_payin(); - auto timestamp = QDateTime::fromMSecsSinceEpoch(command.timestamp_ms()); - // unsigned_payin_data - serialized payin. binary - emit signedPayinRequested(command.settlement_id(), BinaryData::fromString(command.unsigned_payin_data()) - , BinaryData::fromString(command.payin_hash()), timestamp); - break; - } - - default: - break; - } - // if not processed - not RFQ releated message. not error -} diff --git a/BlockSettleUILib/Trading/RFQReplyWidget.h b/BlockSettleUILib/Trading/RFQReplyWidget.h deleted file mode 100644 index 0ce6b9680..000000000 --- a/BlockSettleUILib/Trading/RFQReplyWidget.h +++ /dev/null @@ -1,195 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __RFQ_REPLY_WIDGET_H__ -#define __RFQ_REPLY_WIDGET_H__ - -#include -#include -#include -#include -#include -#include - -#include "BSErrorCode.h" -#include "CoinControlModel.h" -#include "CommonTypes.h" -#include "TabWithShortcut.h" -#include "UtxoReservationToken.h" -#include "HDPath.h" - -namespace Ui { - class RFQReplyWidget; -} -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - namespace hd { - class Wallet; - } - class WalletsManager; - } - class SettlementAddressEntry; - class SecurityStatsCollector; - class UTXOReservationManager; -} -class ApplicationSettings; -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class AutoSignScriptProvider; -class BaseCelerClient; -class ConnectionManager; -class DialogManager; -class MDCallbacksQt; -class OrderListModel; -class QuoteProvider; -class WalletSignerContainer; - -namespace Blocksettle { - namespace Communication { - namespace ProxyTerminalPb { - class Response; - class Response_UpdateOrders; - } - } -} - -namespace bs { - namespace ui { - struct SubmitQuoteReplyData; - } -} - -class RFQReplyWidget : public TabWithShortcut -{ -Q_OBJECT - -public: - RFQReplyWidget(QWidget* parent = nullptr); - ~RFQReplyWidget() override; - - void init(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , OrderListModel *orderListModel); - - void setWalletsManager(const std::shared_ptr &); - - void shortcutActivated(ShortcutType s) override; - -signals: - void orderFilled(); - void requestPrimaryWalletCreation(); - - void sendUnsignedPayinToPB(const std::string& settlementId, const bs::network::UnsignedPayinData& unsignedPayinData); - void sendSignedPayinToPB(const std::string& settlementId, const BinaryData& signedPayin); - void sendSignedPayoutToPB(const std::string& settlementId, const BinaryData& signedPayout); - - void cancelXBTTrade(const std::string& settlementId); - void cancelCCTrade(const std::string& clientOrderId); - - void unsignedPayinRequested(const std::string& settlementId); - void signedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash, QDateTime timestamp); - void signedPayinRequested(const std::string& settlementId, const BinaryData& unsignedPayin - , const BinaryData &payinHash, QDateTime timestamp); - -public slots: - void forceCheckCondition(); - - void onMessageFromPB(const Blocksettle::Communication::ProxyTerminalPb::Response &response); - void onUserConnected(const bs::network::UserType &); - -private slots: - void onOrder(const bs::network::Order &o); - void onQuoteCancelled(const QString &reqId, bool userCancelled); - void onQuoteRejected(const QString &reqId, const QString &reason); - void onQuoteNotifCancelled(const QString &reqId); - - void saveTxData(QString orderId, std::string txData); - void onSignTxRequested(QString orderId, QString reqId, QDateTime timestamp); - void onConnectedToCeler(); - void onDisconnectedFromCeler(); - void onEnterKeyPressed(const QModelIndex &index); - void onSelected(const QString& productGroup, const bs::network::QuoteReqNotification& request, double indicBid, double indicAsk); - void onTransactionError(const std::string &id, bs::error::ErrorCode code, const QString& error); - - void onReplied(const std::shared_ptr &data); - void onPulled(const std::string& settlementId, const std::string& reqId, const std::string& reqSessToken); - - void onCancelXBTTrade(const std::string& settlementId); - void onCancelCCTrade(const std::string& clientOrderId); - void onSettlementComplete(const std::string &id); - -private: - void onResetCurrentReservation(const std::shared_ptr &data); - void showSettlementDialog(QDialog *dlg); - bool checkConditions(const QString& productGroup, const bs::network::QuoteReqNotification& request); - void popShield(); - void showEditableRFQPage(); - void eraseReply(const QString &reqId); - -protected: - void hideEvent(QHideEvent* event) override; - -private: - struct SentXbtReply - { - std::shared_ptr xbtWallet; - bs::Address authAddr; - std::vector utxosPayinFixed; - bs::UtxoReservationToken utxoRes; - std::unique_ptr walletPurpose; - }; - - struct SentCCReply - { - std::string recipientAddress; - std::string requestorAuthAddress; - std::shared_ptr xbtWallet; - bs::UtxoReservationToken utxoRes; - std::unique_ptr walletPurpose; - }; - -private: - std::unique_ptr ui_; - std::shared_ptr logger_; - std::shared_ptr celerClient_; - std::shared_ptr quoteProvider_; - std::shared_ptr authAddressManager_; - std::shared_ptr assetManager_; - std::shared_ptr walletsManager_; - std::shared_ptr dialogManager_; - std::shared_ptr signingContainer_; - std::shared_ptr armory_; - std::shared_ptr appSettings_; - std::shared_ptr connectionManager_; - std::shared_ptr autoSignProvider_; - std::shared_ptr utxoReservationManager_; - - std::unordered_map sentXbtReplies_; - std::unordered_map sentCCReplies_; - std::shared_ptr statsCollector_; - std::unordered_map sentReplyToSettlementsIds_, settlementToReplyIds_; -}; - -#endif // __RFQ_REPLY_WIDGET_H__ diff --git a/BlockSettleUILib/Trading/RFQReplyWidget.ui b/BlockSettleUILib/Trading/RFQReplyWidget.ui deleted file mode 100644 index 2b898e9b3..000000000 --- a/BlockSettleUILib/Trading/RFQReplyWidget.ui +++ /dev/null @@ -1,498 +0,0 @@ - - - - RFQReplyWidget - - - - 0 - 0 - 754 - 601 - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - QUOTE REQUEST BLOTTER - - - true - - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - - - - - 16777215 - 16777215 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - - 16777215 - 30 - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - TRADE BLOTTER - - - true - - - - - - - - - - - 16777215 - 16777215 - - - - QAbstractItemView::NoEditTriggers - - - true - - - true - - - true - - - true - - - 92 - - - - - - - - - - - - - - 0 - 0 - - - - - 300 - 0 - - - - - 300 - 16777215 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - 0 - 0 - - - - - - - 0 - 0 - - - - - 350 - 16777215 - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - QUOTE RESPONSE - - - true - - - - - - - - - - - 0 - 0 - - - - - 0 - 460 - - - - - 16777215 - 16777215 - - - - true - - - - - - - - - - - - 0 - 0 - - - - - 0 - 120 - - - - - 16777215 - 120 - - - - - - - - - - - - OrdersView - QTreeView -
OrdersView.h
-
- - QuoteRequestsWidget - QWidget -
QuoteRequestsWidget.h
- 1 -
- - bs::ui::RFQDealerReply - QWidget -
RFQDealerReply.h
-
- - RFQShieldPage - QWidget -
RFQShieldPage.h
- 1 -
- - AutoSignQuoteWidget - QWidget -
AutoSignQuoteWidget.h
- 1 -
-
- - -
diff --git a/BlockSettleUILib/Trading/RFQRequestWidget.cpp b/BlockSettleUILib/Trading/RFQRequestWidget.cpp deleted file mode 100644 index 35355dcbf..000000000 --- a/BlockSettleUILib/Trading/RFQRequestWidget.cpp +++ /dev/null @@ -1,529 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RFQRequestWidget.h" - -#include -#include - -#include "ApplicationSettings.h" -#include "AuthAddressManager.h" -#include "AutoSignQuoteProvider.h" -#include "CelerClient.h" -#include "CurrencyPair.h" -#include "DialogManager.h" -#include "NotificationCenter.h" -#include "OrderListModel.h" -#include "OrdersView.h" -#include "QuoteProvider.h" -#include "RFQDialog.h" -#include "RfqStorage.h" -#include "WalletSignerContainer.h" -#include "Wallets/SyncWalletsManager.h" -#include "Wallets/SyncHDWallet.h" -#include "UserScriptRunner.h" -#include "UtxoReservationManager.h" -#include "MDCallbacksQt.h" - -#include "bs_proxy_terminal_pb.pb.h" - -#include "ui_RFQRequestWidget.h" - -namespace { - enum class RFQPages : int - { - ShieldPage = 0, - EditableRFQPage - }; -} - -RFQRequestWidget::RFQRequestWidget(QWidget* parent) - : TabWithShortcut(parent) - , ui_(new Ui::RFQRequestWidget()) -{ - rfqStorage_ = std::make_shared(); - - ui_->setupUi(this); - ui_->shieldPage->setTabType(QLatin1String("trade")); - - connect(ui_->shieldPage, &RFQShieldPage::requestPrimaryWalletCreation, this, &RFQRequestWidget::requestPrimaryWalletCreation); - connect(ui_->shieldPage, &RFQShieldPage::loginRequested, this, &RFQRequestWidget::loginRequested); - - ui_->pageRFQTicket->setSubmitRFQ([this] - (const std::string &id, const bs::network::RFQ& rfq, bs::UtxoReservationToken utxoRes) - { - onRFQSubmit(id, rfq, std::move(utxoRes)); - }); - ui_->pageRFQTicket->setCancelRFQ([this] (const std::string &id) - { - onRFQCancel(id); - }); - - ui_->shieldPage->showShieldLoginToSubmitRequired(); - - ui_->pageRFQTicket->lineEditAmount()->installEventFilter(this); - popShield(); -} - -RFQRequestWidget::~RFQRequestWidget() = default; - -void RFQRequestWidget::setWalletsManager(const std::shared_ptr &walletsManager) -{ - if (walletsManager_ == nullptr) { - walletsManager_ = walletsManager; - ui_->pageRFQTicket->setWalletsManager(walletsManager); - ui_->shieldPage->init(walletsManager, authAddressManager_, appSettings_); - - if (autoSignProvider_) { - autoSignProvider_->scriptRunner()->setWalletsManager(walletsManager_); - } - - // Do not listen for walletChanged (too verbose and resets UI too often) and walletsReady (to late and resets UI after startup unexpectedly) - connect(walletsManager_.get(), &bs::sync::WalletsManager::CCLeafCreated, this, &RFQRequestWidget::forceCheckCondition); - connect(walletsManager_.get(), &bs::sync::WalletsManager::AuthLeafCreated, this, &RFQRequestWidget::forceCheckCondition); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletDeleted, this, &RFQRequestWidget::forceCheckCondition); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletAdded, this, &RFQRequestWidget::forceCheckCondition); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletsSynchronized, this, &RFQRequestWidget::forceCheckCondition); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletPromotedToPrimary, this, &RFQRequestWidget::forceCheckCondition); - } -} - -void RFQRequestWidget::shortcutActivated(ShortcutType s) -{ - switch (s) { - case ShortcutType::Alt_1 : { - ui_->widgetMarketData->view()->activate(); - } - break; - - case ShortcutType::Alt_2 : { - if (ui_->pageRFQTicket->lineEditAmount()->isVisible()) { - ui_->pageRFQTicket->lineEditAmount()->setFocus(); - } - else { - ui_->pageRFQTicket->setFocus(); - } - } - break; - - case ShortcutType::Alt_3 : { - ui_->treeViewOrders->activate(); - } - break; - - case ShortcutType::Ctrl_S : { - if (ui_->pageRFQTicket->submitButton()->isEnabled()) { - ui_->pageRFQTicket->submitButton()->click(); - } - } - break; - - case ShortcutType::Alt_S : { - if (ui_->pageRFQTicket->isEnabled()) { - ui_->pageRFQTicket->sellButton()->click(); - } - } - break; - - case ShortcutType::Alt_B : { - if (ui_->pageRFQTicket->isEnabled()) { - ui_->pageRFQTicket->buyButton()->click(); - } - } - break; - - case ShortcutType::Alt_P : { - if (ui_->pageRFQTicket->isEnabled()) { - if (ui_->pageRFQTicket->numCcyButton()->isChecked()) { - ui_->pageRFQTicket->denomCcyButton()->click(); - } - else { - ui_->pageRFQTicket->numCcyButton()->click(); - } - } - } - break; - - default : - break; - } -} - -void RFQRequestWidget::setAuthorized(bool authorized) -{ - ui_->widgetMarketData->setAuthorized(authorized); -} - -void RFQRequestWidget::hideEvent(QHideEvent* event) -{ - ui_->pageRFQTicket->onParentAboutToHide(); - QWidget::hideEvent(event); -} - -bool RFQRequestWidget::eventFilter(QObject* sender, QEvent* event) -{ - if (QEvent::KeyPress == event->type() && ui_->pageRFQTicket->lineEditAmount() == sender) { - QKeyEvent *keyEvent = static_cast(event); - if (Qt::Key_Up == keyEvent->key() || Qt::Key_Down == keyEvent->key()) { - QKeyEvent *pEvent = new QKeyEvent(QEvent::KeyPress, keyEvent->key(), keyEvent->modifiers()); - QCoreApplication::postEvent(ui_->widgetMarketData->view(), pEvent); - return true; - } - } - return false; -} - -void RFQRequestWidget::showEditableRFQPage() -{ - ui_->stackedWidgetRFQ->setEnabled(true); - ui_->pageRFQTicket->enablePanel(); - ui_->stackedWidgetRFQ->setCurrentIndex(static_cast(RFQPages::EditableRFQPage)); -} - -void RFQRequestWidget::popShield() -{ - ui_->stackedWidgetRFQ->setEnabled(true); - - ui_->stackedWidgetRFQ->setCurrentIndex(static_cast(RFQPages::ShieldPage)); - ui_->pageRFQTicket->disablePanel(); - ui_->widgetMarketData->view()->setFocus(); -} - -void RFQRequestWidget::initWidgets(const std::shared_ptr& mdProvider - , const std::shared_ptr &mdCallbacks - , const std::shared_ptr &appSettings) -{ - appSettings_ = appSettings; - ui_->widgetMarketData->init(appSettings, ApplicationSettings::Filter_MD_RFQ - , mdProvider, mdCallbacks); - - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, ui_->pageRFQTicket, &RFQTicketXBT::onMDUpdate); -} - -void RFQRequestWidget::init(const std::shared_ptr &logger - , const std::shared_ptr& celerClient - , const std::shared_ptr &authAddressManager - , const std::shared_ptr "eProvider - , const std::shared_ptr &assetManager - , const std::shared_ptr &dialogManager - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &autoSignProvider - , const std::shared_ptr &utxoReservationManager - , OrderListModel *orderListModel) -{ - logger_ = logger; - celerClient_ = celerClient; - authAddressManager_ = authAddressManager; - quoteProvider_ = quoteProvider; - assetManager_ = assetManager; - dialogManager_ = dialogManager; - signingContainer_ = container; - armory_ = armory; - autoSignProvider_ = autoSignProvider; - utxoReservationManager_ = utxoReservationManager; - - if (walletsManager_) { - autoSignProvider_->scriptRunner()->setWalletsManager(walletsManager_); - } - - ui_->pageRFQTicket->init(logger, authAddressManager, assetManager, - quoteProvider, container, armory, utxoReservationManager); - - ui_->treeViewOrders->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - ui_->treeViewOrders->setModel(orderListModel); - ui_->treeViewOrders->initWithModel(orderListModel); - connect(quoteProvider_.get(), &QuoteProvider::quoteOrderFilled, [](const std::string "eId) { - NotificationCenter::notify(bs::ui::NotifyType::CelerOrder, {true, QString::fromStdString(quoteId)}); - }); - connect(quoteProvider_.get(), &QuoteProvider::orderFailed, [](const std::string "eId, const std::string &reason) { - NotificationCenter::notify(bs::ui::NotifyType::CelerOrder - , { false, QString::fromStdString(quoteId), QString::fromStdString(reason) }); - }); - - connect(celerClient_.get(), &BaseCelerClient::OnConnectedToServer, this, &RFQRequestWidget::onConnectedToCeler); - connect(celerClient_.get(), &BaseCelerClient::OnConnectionClosed, this, &RFQRequestWidget::onDisconnectedFromCeler); - - connect((RFQScriptRunner *)autoSignProvider_->scriptRunner(), &RFQScriptRunner::sendRFQ - , ui_->pageRFQTicket, &RFQTicketXBT::onSendRFQ, Qt::QueuedConnection); - connect((RFQScriptRunner *)autoSignProvider_->scriptRunner(), &RFQScriptRunner::cancelRFQ - , ui_->pageRFQTicket, &RFQTicketXBT::onCancelRFQ, Qt::QueuedConnection); - - ui_->pageRFQTicket->disablePanel(); - - connect(authAddressManager_.get(), &AuthAddressManager::AddressListUpdated, this, &RFQRequestWidget::forceCheckCondition); -} - -void RFQRequestWidget::onConnectedToCeler() -{ - marketDataConnection.push_back(connect(ui_->widgetMarketData, &MarketDataWidget::CurrencySelected, - this, &RFQRequestWidget::onCurrencySelected)); - marketDataConnection.push_back(connect(ui_->widgetMarketData, &MarketDataWidget::BidClicked, - this, &RFQRequestWidget::onBidClicked)); - marketDataConnection.push_back(connect(ui_->widgetMarketData, &MarketDataWidget::AskClicked, - this, &RFQRequestWidget::onAskClicked)); - marketDataConnection.push_back(connect(ui_->widgetMarketData, &MarketDataWidget::MDHeaderClicked, - this, &RFQRequestWidget::onDisableSelectedInfo)); - marketDataConnection.push_back(connect(ui_->widgetMarketData, &MarketDataWidget::clicked, - this, &RFQRequestWidget::onRefreshFocus)); - - ui_->shieldPage->showShieldSelectTargetTrade(); - popShield(); -} - -void RFQRequestWidget::onDisconnectedFromCeler() -{ - for (QMetaObject::Connection &conn : marketDataConnection) { - QObject::disconnect(conn); - } - - ui_->shieldPage->showShieldLoginToSubmitRequired(); - popShield(); -} - -void RFQRequestWidget::onRFQSubmit(const std::string &id, const bs::network::RFQ& rfq - , bs::UtxoReservationToken ccUtxoRes) -{ - auto authAddr = ui_->pageRFQTicket->selectedAuthAddress(); - - auto xbtWallet = ui_->pageRFQTicket->xbtWallet(); - auto fixedXbtInputs = ui_->pageRFQTicket->fixedXbtInputs(); - - std::unique_ptr purpose; - if (xbtWallet && !xbtWallet->canMixLeaves()) { - auto walletType = ui_->pageRFQTicket->xbtWalletType(); - purpose.reset(new bs::hd::Purpose(UiUtils::getHwWalletPurpose(walletType))); - } - - RFQDialog* dialog = new RFQDialog(logger_, id, rfq, quoteProvider_ - , authAddressManager_, assetManager_, walletsManager_, signingContainer_ - , armory_, celerClient_, appSettings_, rfqStorage_, xbtWallet - , ui_->pageRFQTicket->recvXbtAddressIfSet(), authAddr, utxoReservationManager_ - , fixedXbtInputs.inputs, std::move(fixedXbtInputs.utxoRes) - , std::move(ccUtxoRes), std::move(purpose), this); - - connect(this, &RFQRequestWidget::unsignedPayinRequested, dialog, &RFQDialog::onUnsignedPayinRequested); - connect(this, &RFQRequestWidget::signedPayoutRequested, dialog, &RFQDialog::onSignedPayoutRequested); - connect(this, &RFQRequestWidget::signedPayinRequested, dialog, &RFQDialog::onSignedPayinRequested); - connect(dialog, &RFQDialog::accepted, this, &RFQRequestWidget::onRFQAccepted); - connect(dialog, &RFQDialog::expired, this, &RFQRequestWidget::onRFQExpired); - connect(dialog, &RFQDialog::cancelled, this, &RFQRequestWidget::onRFQCancelled); - - dialogManager_->adjustDialogPosition(dialog); - dialog->show(); - - const auto &itDlg = dialogs_.find(id); - if (itDlg != dialogs_.end()) { //np, most likely a resend from script - itDlg->second->deleteLater(); - itDlg->second = dialog; - } - else { - dialogs_[id] = dialog; - } - ui_->pageRFQTicket->resetTicket(); - - const auto& currentInfo = ui_->widgetMarketData->getCurrentlySelectedInfo(); - ui_->pageRFQTicket->SetProductAndSide(currentInfo.productGroup_ - , currentInfo.currencyPair_, currentInfo.bidPrice_, currentInfo.offerPrice_ - , bs::network::Side::Undefined); - - std::vector closedDialogs; - for (const auto &dlg : dialogs_) { - if (dlg.second->isHidden()) { - dlg.second->deleteLater(); - closedDialogs.push_back(dlg.first); - } - } - for (const auto &dlg : closedDialogs) { - dialogs_.erase(dlg); - } -} - -void RFQRequestWidget::onRFQCancel(const std::string &id) -{ - deleteDialog(id); -} - -void RFQRequestWidget::deleteDialog(const std::string &rfqId) -{ - const auto &itDlg = dialogs_.find(rfqId); - if (itDlg == dialogs_.end()) { - return; - } - itDlg->second->cancel(); - itDlg->second->deleteLater(); - dialogs_.erase(itDlg); -} - -bool RFQRequestWidget::checkConditions(const MarketSelectedInfo& selectedInfo) -{ - ui_->stackedWidgetRFQ->setEnabled(true); - using UserType = CelerClient::CelerUserType; - const UserType userType = celerClient_->celerUserType(); - - using GroupType = RFQShieldPage::ProductType; - const GroupType group = RFQShieldPage::getProductGroup(selectedInfo.productGroup_); - - switch (userType) { - case UserType::Market: { - if (group == GroupType::SpotFX || group == GroupType::SpotXBT) { - ui_->shieldPage->showShieldReservedTradingParticipant(); - popShield(); - return false; - } else if (checkWalletSettings(group, selectedInfo)) { - return false; - } - break; - } - case UserType::Dealing: - case UserType::Trading: { - if ((group == GroupType::SpotXBT || group == GroupType::PrivateMarket) && - checkWalletSettings(group, selectedInfo)) { - return false; - } - break; - } - default: break; - } - - if (ui_->stackedWidgetRFQ->currentIndex() != static_cast(RFQPages::EditableRFQPage)) { - showEditableRFQPage(); - } - - return true; -} - -bool RFQRequestWidget::checkWalletSettings(bs::network::Asset::Type productType, const MarketSelectedInfo& selectedInfo) -{ - const CurrencyPair cp(selectedInfo.currencyPair_.toStdString()); - const QString currentProduct = QString::fromStdString(cp.NumCurrency()); - if (ui_->shieldPage->checkWalletSettings(productType, currentProduct)) { - popShield(); - return true; - } - - return false; -} - -void RFQRequestWidget::forceCheckCondition() -{ - if (!ui_->widgetMarketData || !celerClient_->IsConnected()) { - return; - } - - const auto& currentInfo = ui_->widgetMarketData->getCurrentlySelectedInfo(); - if (!currentInfo.isValid()) { - return; - } - onCurrencySelected(currentInfo); -} - -void RFQRequestWidget::onCurrencySelected(const MarketSelectedInfo& selectedInfo) -{ - if (!checkConditions(selectedInfo)) { - return; - } - ui_->pageRFQTicket->setSecurityId(selectedInfo.productGroup_ - , selectedInfo.currencyPair_, selectedInfo.bidPrice_, selectedInfo.offerPrice_); -} - -void RFQRequestWidget::onBidClicked(const MarketSelectedInfo& selectedInfo) -{ - if (!checkConditions(selectedInfo)) { - return; - } - ui_->pageRFQTicket->setSecuritySell(selectedInfo.productGroup_ - , selectedInfo.currencyPair_, selectedInfo.bidPrice_, selectedInfo.offerPrice_); -} - -void RFQRequestWidget::onAskClicked(const MarketSelectedInfo& selectedInfo) -{ - if (!checkConditions(selectedInfo)) { - return; - } - ui_->pageRFQTicket->setSecurityBuy(selectedInfo.productGroup_ - , selectedInfo.currencyPair_, selectedInfo.bidPrice_, selectedInfo.offerPrice_); -} - -void RFQRequestWidget::onDisableSelectedInfo() -{ - ui_->shieldPage->showShieldSelectTargetTrade(); - popShield(); -} - -void RFQRequestWidget::onRefreshFocus() -{ - if (ui_->stackedWidgetRFQ->currentIndex() == static_cast(RFQPages::EditableRFQPage)) { - ui_->pageRFQTicket->lineEditAmount()->setFocus(); - } -} - -void RFQRequestWidget::onMessageFromPB(const Blocksettle::Communication::ProxyTerminalPb::Response &response) -{ - switch (response.data_case()) { - case Blocksettle::Communication::ProxyTerminalPb::Response::kSendUnsignedPayin: { - const auto &command = response.send_unsigned_payin(); - emit unsignedPayinRequested(command.settlement_id()); - break; - } - - case Blocksettle::Communication::ProxyTerminalPb::Response::kSignPayout: { - const auto &command = response.sign_payout(); - auto timestamp = QDateTime::fromMSecsSinceEpoch(command.timestamp_ms()); - // payin_data - payin hash . binary - emit signedPayoutRequested(command.settlement_id(), BinaryData::fromString(command.payin_data()), timestamp); - break; - } - - case Blocksettle::Communication::ProxyTerminalPb::Response::kSignPayin: { - const auto &command = response.sign_payin(); - auto timestamp = QDateTime::fromMSecsSinceEpoch(command.timestamp_ms()); - // unsigned_payin_data - serialized payin. binary - emit signedPayinRequested(command.settlement_id(), BinaryData::fromString(command.unsigned_payin_data()) - , BinaryData::fromString(command.payin_hash()), timestamp); - break; - } - - default: - break; - } - // if not processed - not RFQ releated message. not error -} - -void RFQRequestWidget::onUserConnected(const bs::network::UserType &ut) -{ - if (appSettings_->get(ApplicationSettings::AutoStartRFQScript)) { - QTimer::singleShot(1000, [this] { // add some delay to allow initial sync of data - ((RFQScriptRunner *)autoSignProvider_->scriptRunner())->start( - autoSignProvider_->getLastScript()); - }); - } -} - -void RFQRequestWidget::onUserDisconnected() -{ - ((RFQScriptRunner *)autoSignProvider_->scriptRunner())->suspend(); -} - -void RFQRequestWidget::onRFQAccepted(const std::string &id) -{ - ((RFQScriptRunner *)autoSignProvider_->scriptRunner())->rfqAccepted(id); -} - -void RFQRequestWidget::onRFQExpired(const std::string &id) -{ - deleteDialog(id); - ((RFQScriptRunner *)autoSignProvider_->scriptRunner())->rfqExpired(id); -} - -void RFQRequestWidget::onRFQCancelled(const std::string &id) -{ - ((RFQScriptRunner *)autoSignProvider_->scriptRunner())->rfqCancelled(id); -} diff --git a/BlockSettleUILib/Trading/RFQRequestWidget.h b/BlockSettleUILib/Trading/RFQRequestWidget.h deleted file mode 100644 index 556e8532e..000000000 --- a/BlockSettleUILib/Trading/RFQRequestWidget.h +++ /dev/null @@ -1,166 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __RFQ_REQUEST_WIDGET_H__ -#define __RFQ_REQUEST_WIDGET_H__ - -#include -#include -#include - -#include "CommonTypes.h" -#include "MarketDataWidget.h" -#include "TabWithShortcut.h" -#include "UtxoReservationToken.h" - -namespace Ui { - class RFQRequestWidget; -} -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - class WalletsManager; - } - class UTXOReservationManager; -} - -namespace Blocksettle { - namespace Communication { - namespace ProxyTerminalPb { - class Response; - } - } -} - -class ApplicationSettings; -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class AutoSignScriptProvider; -class BaseCelerClient; -class DialogManager; -class MarketDataProvider; -class MDCallbacksQt; -class OrderListModel; -class QuoteProvider; -class RFQDialog; -class RfqStorage; -class WalletSignerContainer; - -class RFQRequestWidget : public TabWithShortcut -{ -Q_OBJECT - -public: - RFQRequestWidget(QWidget* parent = nullptr); - ~RFQRequestWidget() override; - - void initWidgets(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); - - void init(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , OrderListModel *orderListModel); - - void setWalletsManager(const std::shared_ptr &); - - void shortcutActivated(ShortcutType s) override; - - void setAuthorized(bool authorized); - -protected: - void hideEvent(QHideEvent* event) override; - bool eventFilter(QObject* sender, QEvent* event) override; - -signals: - void requestPrimaryWalletCreation(); - void loginRequested(); - - void sendUnsignedPayinToPB(const std::string& settlementId, const bs::network::UnsignedPayinData& unsignedPayinData); - void sendSignedPayinToPB(const std::string& settlementId, const BinaryData& signedPayin); - void sendSignedPayoutToPB(const std::string& settlementId, const BinaryData& signedPayout); - - void cancelXBTTrade(const std::string& settlementId); - void cancelCCTrade(const std::string& orderId); - - void unsignedPayinRequested(const std::string& settlementId); - void signedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash, QDateTime timestamp); - void signedPayinRequested(const std::string& settlementId, const BinaryData& unsignedPayin - , const BinaryData &payinHash, QDateTime timestamp); - -private: - void showEditableRFQPage(); - void popShield(); - - bool checkConditions(const MarketSelectedInfo& productGroup); - bool checkWalletSettings(bs::network::Asset::Type productType - , const MarketSelectedInfo& productGroup); - void onRFQSubmit(const std::string &rfqId, const bs::network::RFQ& rfq - , bs::UtxoReservationToken ccUtxoRes); - void onRFQCancel(const std::string &rfqId); - void deleteDialog(const std::string &rfqId); - -public slots: - void onCurrencySelected(const MarketSelectedInfo& selectedInfo); - void onBidClicked(const MarketSelectedInfo& selectedInfo); - void onAskClicked(const MarketSelectedInfo& selectedInfo); - void onDisableSelectedInfo(); - void onRefreshFocus(); - - void onMessageFromPB(const Blocksettle::Communication::ProxyTerminalPb::Response &response); - void onUserConnected(const bs::network::UserType &); - void onUserDisconnected(); - -private slots: - void onConnectedToCeler(); - void onDisconnectedFromCeler(); - void onRFQAccepted(const std::string &id); - void onRFQExpired(const std::string &id); - void onRFQCancelled(const std::string &id); - -public slots: - void forceCheckCondition(); - -private: - std::unique_ptr ui_; - - std::shared_ptr logger_; - std::shared_ptr celerClient_; - std::shared_ptr quoteProvider_; - std::shared_ptr assetManager_; - std::shared_ptr authAddressManager_; - std::shared_ptr dialogManager_; - - std::shared_ptr walletsManager_; - std::shared_ptr signingContainer_; - std::shared_ptr armory_; - std::shared_ptr appSettings_; - std::shared_ptr autoSignProvider_; - std::shared_ptr utxoReservationManager_; - - std::shared_ptr rfqStorage_; - - QList marketDataConnection; - - std::unordered_map dialogs_; -}; - -#endif // __RFQ_REQUEST_WIDGET_H__ diff --git a/BlockSettleUILib/Trading/RFQRequestWidget.ui b/BlockSettleUILib/Trading/RFQRequestWidget.ui deleted file mode 100644 index 7081d8c5b..000000000 --- a/BlockSettleUILib/Trading/RFQRequestWidget.ui +++ /dev/null @@ -1,308 +0,0 @@ - - - - RFQRequestWidget - - - true - - - - 0 - 0 - 616 - 367 - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - TRADE BLOTTER - - - true - - - - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - true - - - true - - - true - - - 92 - - - - - - - - - - - - - - 0 - 0 - - - - - 270 - 0 - - - - - 250 - 16777215 - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - OrdersView - QTreeView -
OrdersView.h
-
- - RFQShieldPage - QWidget -
RFQShieldPage.h
- 1 -
- - MarketDataWidget - QWidget -
MarketDataWidget.h
- 1 -
- - RFQTicketXBT - QWidget -
RFQTicketXBT.h
- 1 -
-
- - -
diff --git a/BlockSettleUILib/Trading/RFQShieldPage.cpp b/BlockSettleUILib/Trading/RFQShieldPage.cpp deleted file mode 100644 index 53ce9e919..000000000 --- a/BlockSettleUILib/Trading/RFQShieldPage.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RFQShieldPage.h" - -namespace { - // Label texts - const QString shieldLoginToSubmitRFQs = QObject::tr("Login to submit RFQs"); - const QString shieldLoginToResponseRFQs = QObject::tr("Login to submit responsive quotes"); - const QString shieldTradingParticipantOnly = QObject::tr("Reserved for Trading Participants"); - const QString shieldDealingParticipantOnly = QObject::tr("Reserved for Dealing Participants"); - const QString shieldTradeUnselectedTargetRequest = QObject::tr("In the Market Data window, please click on the product / security you wish to trade"); - const QString shieldDealingUnselectedTargetRequest = QObject::tr("In the Quote Request Blotter, please click on the product / security you wish to quote"); -} - -RFQShieldPage::RFQShieldPage(QWidget *parent) : - WalletShieldBase(parent) -{ -} - -RFQShieldPage::~RFQShieldPage() noexcept = default; - -void RFQShieldPage::showShieldLoginToSubmitRequired() -{ - showShield(shieldLoginToSubmitRFQs, tr("Login")); - setShieldButtonAction([this]() { - emit loginRequested(); - }, false); -} - -void RFQShieldPage::showShieldLoginToResponseRequired() -{ - showShield(shieldLoginToResponseRFQs); -} - -void RFQShieldPage::showShieldReservedTradingParticipant() -{ - showShield(shieldTradingParticipantOnly); -} - -void RFQShieldPage::showShieldReservedDealingParticipant() -{ - showShield(shieldDealingParticipantOnly); -} - -void RFQShieldPage::showShieldSelectTargetTrade() -{ - showShield(shieldTradeUnselectedTargetRequest); -} - -void RFQShieldPage::showShieldSelectTargetDealing() -{ - showShield(shieldDealingUnselectedTargetRequest); -} - -void RFQShieldPage::setLoginEnabled(bool enabled) -{ - loginEnabled_ = enabled; -} diff --git a/BlockSettleUILib/Trading/RFQShieldPage.h b/BlockSettleUILib/Trading/RFQShieldPage.h deleted file mode 100644 index 6c7278e72..000000000 --- a/BlockSettleUILib/Trading/RFQShieldPage.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef RFQREPLYLOGINREQUIREDSHIELD_H -#define RFQREPLYLOGINREQUIREDSHIELD_H - -#include "WalletShieldBase.h" - -class RFQShieldPage : public WalletShieldBase -{ - Q_OBJECT - -public: - explicit RFQShieldPage(QWidget *parent = nullptr); - ~RFQShieldPage() noexcept override; - - void showShieldLoginToSubmitRequired(); - void showShieldLoginToResponseRequired(); - void showShieldReservedTradingParticipant(); - void showShieldReservedDealingParticipant(); - void showShieldSelectTargetTrade(); - void showShieldSelectTargetDealing(); - - void setLoginEnabled(bool enabled); - -private: - bool loginEnabled_{}; -}; - -#endif // RFQREPLYLOGINREQUIREDSHIELD_H diff --git a/BlockSettleUILib/Trading/RFQTicketXBT.cpp b/BlockSettleUILib/Trading/RFQTicketXBT.cpp deleted file mode 100644 index 3fcebd39b..000000000 --- a/BlockSettleUILib/Trading/RFQTicketXBT.cpp +++ /dev/null @@ -1,1634 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RFQTicketXBT.h" -#include "ui_RFQTicketXBT.h" - -#include -#include -#include -#include -#include -#include "AssetManager.h" -#include "AuthAddressManager.h" -#include "BSErrorCodeStrings.h" -#include "BSMessageBox.h" -#include "CCAmountValidator.h" -#include "CoinControlDialog.h" -#include "CoinSelection.h" -#include "CurrencyPair.h" -#include "EncryptionUtils.h" -#include "FXAmountValidator.h" -#include "QuoteProvider.h" -#include "SelectedTransactionInputs.h" -#include "SignContainer.h" -#include "TradesUtils.h" -#include "TxClasses.h" -#include "UiUtils.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "XbtAmountValidator.h" -#include "UtxoReservationManager.h" -#include "UtxoReservation.h" -#include "TradeSettings.h" - -#include - - -namespace { - static const QString kEmptyInformationalLabelText = QString::fromStdString("--"); -} - - -RFQTicketXBT::RFQTicketXBT(QWidget* parent) - : QWidget(parent) - , ui_(new Ui::RFQTicketXBT()) -{ - ui_->setupUi(this); - - initProductGroupMap(); - - invalidBalanceFont_ = ui_->labelBalanceValue->font(); - invalidBalanceFont_.setStrikeOut(true); - - ui_->pushButtonCreateWallet->hide(); - ui_->pushButtonCreateWallet->setEnabled(false); - - ccAmountValidator_ = new CCAmountValidator(this); - fxAmountValidator_ = new FXAmountValidator(this); - xbtAmountValidator_ = new XbtAmountValidator(this); - - ui_->lineEditAmount->installEventFilter(this); - - connect(ui_->pushButtonNumCcy, &QPushButton::clicked, this, &RFQTicketXBT::onNumCcySelected); - connect(ui_->pushButtonDenomCcy, &QPushButton::clicked, this, &RFQTicketXBT::onDenomCcySelected); - - connect(ui_->pushButtonSell, &QPushButton::clicked, this, &RFQTicketXBT::onSellSelected); - connect(ui_->pushButtonBuy, &QPushButton::clicked, this, &RFQTicketXBT::onBuySelected); - - connect(ui_->pushButtonSubmit, &QPushButton::clicked, this, &RFQTicketXBT::submitButtonClicked); - connect(ui_->toolButtonXBTInputsSend, &QPushButton::clicked, this, &RFQTicketXBT::showCoinControl); - connect(ui_->toolButtonMax, &QPushButton::clicked, this, &RFQTicketXBT::onMaxClicked); - connect(ui_->comboBoxXBTWalletsRecv, qOverload(&QComboBox::currentIndexChanged), this, &RFQTicketXBT::walletSelectedRecv); - connect(ui_->comboBoxXBTWalletsSend, qOverload(&QComboBox::currentIndexChanged), this, &RFQTicketXBT::walletSelectedSend); - - connect(ui_->pushButtonCreateWallet, &QPushButton::clicked, this, &RFQTicketXBT::onCreateWalletClicked); - - connect(ui_->lineEditAmount, &QLineEdit::textEdited, this, &RFQTicketXBT::onAmountEdited); - - connect(ui_->authenticationAddressComboBox, qOverload(&QComboBox::currentIndexChanged), this, &RFQTicketXBT::onAuthAddrChanged); - - ui_->comboBoxXBTWalletsRecv->setEnabled(false); - ui_->comboBoxXBTWalletsSend->setEnabled(false); - - disablePanel(); -} - -RFQTicketXBT::~RFQTicketXBT() = default; - -void RFQTicketXBT::resetTicket() -{ - ui_->labelProductGroup->setText(kEmptyInformationalLabelText); - ui_->labelSecurityId->setText(kEmptyInformationalLabelText); - ui_->labelIndicativePrice->setText(kEmptyInformationalLabelText); - - currentBidPrice_ = kEmptyInformationalLabelText; - currentOfferPrice_ = kEmptyInformationalLabelText; - currentGroupType_ = ProductGroupType::GroupNotSelected; - - ui_->lineEditAmount->setValidator(nullptr); - ui_->lineEditAmount->setEnabled(false); - ui_->lineEditAmount->clear(); - - rfqMap_.clear(); - - HideRFQControls(); - - updatePanel(); -} - -bs::FixedXbtInputs RFQTicketXBT::fixedXbtInputs() -{ - return std::move(fixedXbtInputs_); -} - -void RFQTicketXBT::init(const std::shared_ptr &logger - , const std::shared_ptr &authAddressManager - , const std::shared_ptr& assetManager - , const std::shared_ptr "eProvider - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &utxoReservationManager) -{ - logger_ = logger; - authAddressManager_ = authAddressManager; - assetManager_ = assetManager; - signingContainer_ = container; - armory_ = armory; - utxoReservationManager_ = utxoReservationManager; - - if (signingContainer_) { - connect(signingContainer_.get(), &SignContainer::ready, this, &RFQTicketXBT::onSignerReady); - } - connect(utxoReservationManager_.get(), &bs::UTXOReservationManager::availableUtxoChanged, - this, &RFQTicketXBT::onUTXOReservationChanged); - - updateSubmitButton(); -} - -std::shared_ptr RFQTicketXBT::getCCWallet(const std::string &cc) const -{ - if (walletsManager_) { - return walletsManager_->getCCWallet(cc); - } - - return nullptr; -} - -void RFQTicketXBT::updatePanel() -{ - const auto selectedSide = getSelectedSide(); - - if (selectedSide == bs::network::Side::Undefined) { - showHelp(tr("Click on desired product in MD list")); - ui_->pushButtonSubmit->setEnabled(false); - return; - } - - ui_->toolButtonMax->setVisible(selectedSide == bs::network::Side::Sell); - - if (currentGroupType_ != ProductGroupType::FXGroupType) { - const bool buyXBT = getProductToRecv() == UiUtils::XbtCurrency; - ui_->recAddressLayout->setVisible(buyXBT); - ui_->XBTWalletLayoutRecv->setVisible(buyXBT); - ui_->XBTWalletLayoutSend->setVisible(!buyXBT); - } - - updateIndicativePrice(); - updateBalances(); - updateSubmitButton(); -} - -void RFQTicketXBT::onHDLeafCreated(const std::string& ccName) -{ - if (getProduct().toStdString() != ccName) { - return; - } - - auto leaf = walletsManager_->getCCWallet(ccName); - if (leaf == nullptr) { - showHelp(tr("Leaf not created")); - return; - } - - ui_->pushButtonCreateWallet->hide(); - ui_->pushButtonCreateWallet->setText(tr("Create Wallet")); - ui_->pushButtonSubmit->show(); - - clearHelp(); - updatePanel(); -} - -void RFQTicketXBT::onCreateHDWalletError(const std::string& ccName, bs::error::ErrorCode result) -{ - if (getProduct().toStdString() != ccName) { - return; - } - - showHelp(tr("Failed to create wallet: %1").arg(bs::error::ErrorCodeToString(result))); -} - -void RFQTicketXBT::updateBalances() -{ - const auto balance = getBalanceInfo(); - - QString amountString; - switch (balance.productType) { - case ProductGroupType::XBTGroupType: - amountString = UiUtils::displayAmount(balance.amount); - break; - case ProductGroupType::CCGroupType: - amountString = UiUtils::displayCCAmount(balance.amount); - break; - case ProductGroupType::FXGroupType: - amountString = UiUtils::displayCurrencyAmount(balance.amount); - break; - } - - QString text = tr("%1 %2").arg(amountString).arg(balance.product); - ui_->labelBalanceValue->setText(text); -} - -RFQTicketXBT::BalanceInfoContainer RFQTicketXBT::getBalanceInfo() const -{ - BalanceInfoContainer balance; - - QString productToSpend = getProductToSpend(); - - if (UiUtils::XbtCurrency == productToSpend) { - balance.amount = getXbtBalance().GetValueBitcoin(); - balance.product = UiUtils::XbtCurrency; - balance.productType = ProductGroupType::XBTGroupType; - } else { - if (currentGroupType_ == ProductGroupType::CCGroupType) { - balance.amount = utxoReservationManager_->getAvailableCCUtxoSum(getProduct().toStdString()); - balance.product = productToSpend; - balance.productType = ProductGroupType::CCGroupType; - } else { - const double divisor = std::pow(10, UiUtils::GetAmountPrecisionFX()); - const double intBalance = std::floor((assetManager_ ? - assetManager_->getBalance(productToSpend.toStdString(), bs::UTXOReservationManager::kIncludeZcRequestor, nullptr) : 0.0) * divisor); - balance.amount = intBalance / divisor; - balance.product = productToSpend; - balance.productType = ProductGroupType::FXGroupType; - } - } - - return balance; -} - -QString RFQTicketXBT::getProduct() const -{ - return currentProduct_; -} - -void RFQTicketXBT::setWalletsManager(const std::shared_ptr &walletsManager) -{ - walletsManager_ = walletsManager; - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletsSynchronized, this, &RFQTicketXBT::walletsLoaded); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletAdded, this, &RFQTicketXBT::walletsLoaded); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletDeleted, this, &RFQTicketXBT::walletsLoaded); - connect(walletsManager_.get(), &bs::sync::WalletsManager::settlementLeavesLoaded, this, &RFQTicketXBT::onSettlLeavesLoaded); - - connect(walletsManager_.get(), &bs::sync::WalletsManager::CCLeafCreated, this, &RFQTicketXBT::onHDLeafCreated); - connect(walletsManager_.get(), &bs::sync::WalletsManager::CCLeafCreateFailed, this, &RFQTicketXBT::onCreateHDWalletError); - - walletsLoaded(); - - auto updateAuthAddresses = [this] { - UiUtils::fillAuthAddressesComboBoxWithSubmitted(ui_->authenticationAddressComboBox, authAddressManager_); - onAuthAddrChanged(ui_->authenticationAddressComboBox->currentIndex()); - authAddr_ = authAddressManager_->getDefault(); - if (authKey_.empty()) { - onSettlLeavesLoaded(0); - } - }; - updateAuthAddresses(); - connect(authAddressManager_.get(), &AuthAddressManager::AddressListUpdated, this, updateAuthAddresses); - - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, [this] { - // This will update balance after receiving ZC - updatePanel(); - }); -} - -void RFQTicketXBT::walletsLoaded() -{ - if (!signingContainer_ || !walletsManager_ || walletsManager_->hdWallets().empty()) { - return; - } - - ui_->comboBoxXBTWalletsRecv->clear(); - ui_->comboBoxXBTWalletsSend->clear(); - - if (signingContainer_->isOffline()) { - ui_->comboBoxXBTWalletsRecv->setEnabled(false); - ui_->comboBoxXBTWalletsSend->setEnabled(false); - } else { - ui_->comboBoxXBTWalletsRecv->setEnabled(true); - ui_->comboBoxXBTWalletsSend->setEnabled(true); - - UiUtils::fillHDWalletsComboBox(ui_->comboBoxXBTWalletsRecv, walletsManager_, UiUtils::WalletsTypes::All); - // CC does not support to send from hardware wallets - int sendWalletTypes = (currentGroupType_ == ProductGroupType::CCGroupType) ? - UiUtils::WalletsTypes::Full : (UiUtils::WalletsTypes::Full | UiUtils::WalletsTypes::HardwareSW); - UiUtils::fillHDWalletsComboBox(ui_->comboBoxXBTWalletsSend, walletsManager_, sendWalletTypes); - - const auto walletId = walletsManager_->getDefaultSpendWalletId(); - - auto selected = UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWalletsSend, walletId, static_cast(sendWalletTypes)); - if (selected == -1) { - auto primaryWallet = walletsManager_->getPrimaryWallet(); - if (primaryWallet != nullptr) { - UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWalletsSend, primaryWallet->walletId(), static_cast(sendWalletTypes)); - } - } - - selected = UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWalletsRecv, walletId, UiUtils::WalletsTypes::All); - if (selected == -1) { - auto primaryWallet = walletsManager_->getPrimaryWallet(); - if (primaryWallet != nullptr) { - UiUtils::selectWalletInCombobox(ui_->comboBoxXBTWalletsRecv, primaryWallet->walletId(), UiUtils::WalletsTypes::All); - } - } - } - - productSelectionChanged(); -} - -void RFQTicketXBT::onSignerReady() -{ - updateSubmitButton(); - ui_->receivingWalletWidgetRecv->setEnabled(!signingContainer_->isOffline()); - ui_->receivingWalletWidgetSend->setEnabled(!signingContainer_->isOffline()); -} - -void RFQTicketXBT::fillRecvAddresses() -{ - auto recvWallet = getRecvXbtWallet(); - if (recvWallet) { - if (!recvWallet->canMixLeaves()) { - auto xbtGroup = recvWallet->getGroup(recvWallet->getXBTGroupType()); - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWalletsRecv); - UiUtils::fillRecvAddressesComboBox(ui_->receivingAddressComboBox, { xbtGroup->getLeaf(purpose) }); - } - else { - UiUtils::fillRecvAddressesComboBoxHDWallet(ui_->receivingAddressComboBox, recvWallet, true); - } - } -} - -bool RFQTicketXBT::preSubmitCheck() -{ - if (currentGroupType_ == ProductGroupType::XBTGroupType) { - - if (authAddressManager_->GetState(authAddr_) == AuthAddressManager::AuthAddressState::Verified) { - return true; - } - - const auto qty = getQuantity(); - const auto& tradeSettings = authAddressManager_->tradeSettings(); - assert(tradeSettings); - - bool validAmount = false; - if (currentProduct_ == UiUtils::XbtCurrency) { - validAmount = tradeSettings->xbtTier1Limit > bs::XBTAmount(qty).GetValue(); - } else { - const double indPrice = getIndicativePrice(); - bs::XBTAmount price(indPrice * (1 + (tradeSettings->xbtPriceBand / 100))); - validAmount = price > bs::XBTAmount(qty); - } - - if (!validAmount) { - auto amountStr = UiUtils::displayQuantity(bs::XBTAmount(tradeSettings->xbtTier1Limit).GetValueBitcoin(), bs::network::XbtCurrency); - BSMessageBox(BSMessageBox::info - , tr("Notice"), tr("Authentication Address not verified") - , tr("Trades above %1 are not permitted for non-verified Authentication Addresses. " - "To verify your Authentication Address, execute three trades below the %2 threshold, " - "and BlockSettle will validate the address during its next cycle.").arg(amountStr).arg(amountStr), this).exec(); - return false; - } - } - - return true; -} - -void RFQTicketXBT::showCoinControl() -{ - const auto xbtWallet = getSendXbtWallet(); - if (!xbtWallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find XBT wallet"); - return; - } - auto walletId = xbtWallet->walletId(); - ui_->toolButtonXBTInputsSend->setEnabled(false); - - // Need to release current reservation to be able select them back - fixedXbtInputs_.utxoRes.release(); - - std::vector utxos; - if (!xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWalletsSend); - utxos = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcRequestor); - } - else { - utxos = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet->walletId(), bs::UTXOReservationManager::kIncludeZcRequestor); - } - - ui_->toolButtonXBTInputsSend->setEnabled(true); - const bool useAutoSel = fixedXbtInputs_.inputs.empty(); - - auto inputs = std::make_shared(utxos); - // Set this to false is needed otherwise current selection would be cleared - inputs->SetUseAutoSel(useAutoSel); - for (const auto &utxo : fixedXbtInputs_.inputs) { - inputs->SetUTXOSelection(utxo.first.getTxHash(), utxo.first.getTxOutIndex()); - } - - CoinControlDialog dialog(inputs, true, this); - int rc = dialog.exec(); - if (rc != QDialog::Accepted) { - return; - } - - auto selectedInputs = dialog.selectedInputs(); - if (bs::UtxoReservation::instance()->containsReservedUTXO(selectedInputs)) { - BSMessageBox(BSMessageBox::critical, tr("UTXO reservation failed"), - tr("Some of selected UTXOs has been already reserved"), this).exec(); - showCoinControl(); - return; - } - - fixedXbtInputs_.inputs.clear(); - for (const auto &selectedInput : selectedInputs) { - fixedXbtInputs_.inputs.emplace(selectedInput, walletId); - } - - if (!selectedInputs.empty()) { - fixedXbtInputs_.utxoRes = utxoReservationManager_->makeNewReservation(selectedInputs); - } - - updateBalances(); - updateSubmitButton(); -} - -void RFQTicketXBT::walletSelectedRecv(int index) -{ - productSelectionChanged(); -} - -void RFQTicketXBT::walletSelectedSend(int index) -{ - productSelectionChanged(); -} - -void RFQTicketXBT::SetProductGroup(const QString& productGroup) -{ - currentGroupType_ = getProductGroupType(productGroup); - if (currentGroupType_ != ProductGroupType::GroupNotSelected) { - ui_->labelProductGroup->setText(productGroup); - - ui_->lineBeforeProduct->setVisible(true); - ui_->verticalWidgetSelectedProduct->setVisible(true); - - ui_->lineBeforeBalance->setVisible(true); - ui_->balanceLayout->setVisible(true); - - if (currentGroupType_ != ProductGroupType::FXGroupType) { - ui_->groupBoxSettlementInputs->setVisible(true); - - ui_->authAddressLayout->setVisible(currentGroupType_ == ProductGroupType::XBTGroupType); - } else { - ui_->groupBoxSettlementInputs->setVisible(false); - } - } else { - ui_->labelProductGroup->setText(tr("XXX")); - } - - walletsLoaded(); -} - -void RFQTicketXBT::SetCurrencyPair(const QString& currencyPair) -{ - if (currentGroupType_ != ProductGroupType::GroupNotSelected) { - clearHelp(); - - ui_->labelSecurityId->setText(currencyPair); - - CurrencyPair cp(currencyPair.toStdString()); - - currentProduct_ = QString::fromStdString(cp.NumCurrency()); - contraProduct_ = QString::fromStdString(cp.DenomCurrency()); - - ui_->pushButtonNumCcy->setText(currentProduct_); - ui_->pushButtonNumCcy->setChecked(true); - - ui_->pushButtonDenomCcy->setText(contraProduct_); - ui_->pushButtonDenomCcy->setChecked(false); - - ui_->pushButtonDenomCcy->setEnabled(currentGroupType_ != ProductGroupType::CCGroupType); - } -} - -void RFQTicketXBT::SetProductAndSide(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice, bs::network::Side::Type side) -{ - resetTicket(); - - if (productGroup.isEmpty() || currencyPair.isEmpty()) { - return; - } - - SetProductGroup(productGroup); - SetCurrencyPair(currencyPair); - SetCurrentIndicativePrices(bidPrice, offerPrice); - - if (side == bs::network::Side::Type::Undefined) { - side = getLastSideSelection(getProduct().toStdString(), currencyPair.toStdString()); - } - - ui_->pushButtonSell->setChecked(side == bs::network::Side::Sell); - ui_->pushButtonBuy->setChecked(side == bs::network::Side::Buy); - - productSelectionChanged(); -} - -void RFQTicketXBT::setSecurityId(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice) -{ - SetProductAndSide(productGroup, currencyPair, bidPrice, offerPrice, bs::network::Side::Undefined); -} - -void RFQTicketXBT::setSecurityBuy(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice) -{ - SetProductAndSide(productGroup, currencyPair, bidPrice, offerPrice, bs::network::Side::Buy); -} - -void RFQTicketXBT::setSecuritySell(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice) -{ - SetProductAndSide(productGroup, currencyPair, bidPrice, offerPrice, bs::network::Side::Sell); -} - -bs::network::Side::Type RFQTicketXBT::getSelectedSide() const -{ - if (currentGroupType_ == ProductGroupType::GroupNotSelected) { - return bs::network::Side::Undefined; - } - - if (ui_->pushButtonSell->isChecked()) { - return bs::network::Side::Sell; - } - - return bs::network::Side::Buy; -} - -void RFQTicketXBT::onSettlLeavesLoaded(unsigned int) -{ - decltype(deferredRFQs_) tmpRFQs; - tmpRFQs.swap(deferredRFQs_); - logger_->debug("[RFQTicketXBT::onSettlLeavesLoaded] sending {} deferred RFQ[s]", tmpRFQs.size()); - - if (authKey_.empty()) { - if (authAddr_.empty()) { - logger_->warn("[RFQTicketXBT::onSettlLeavesLoaded] no default auth address"); - return; - } - const auto &cbPubKey = [this, tmpRFQs](const SecureBinaryData &pubKey) { - authKey_ = pubKey.toHexStr(); - for (const auto &id : tmpRFQs) { - sendRFQ(id); - } - }; - const auto settlLeaf = walletsManager_->getSettlementLeaf(authAddr_); - if (!settlLeaf) { - logger_->warn("[RFQTicketXBT::onSettlLeavesLoaded] no settlement leaf" - " for auth address {}", authAddr_.display()); - return; - } - settlLeaf->getRootPubkey(cbPubKey); - } - else { - for (const auto &id : tmpRFQs) { - sendRFQ(id); - } - } -} - -std::string RFQTicketXBT::authKey() const -{ - return authKey_; -} - -void RFQTicketXBT::onAuthAddrChanged(int index) -{ - auto addressString = ui_->authenticationAddressComboBox->itemText(index).toStdString(); - if (addressString.empty()) { - return; - } - - authAddr_ = bs::Address::fromAddressString(addressString); - - authKey_.clear(); - const auto settlLeaf = walletsManager_->getSettlementLeaf(authAddr_); - - const auto &cbPubKey = [this](const SecureBinaryData &pubKey) { - authKey_ = pubKey.toHexStr(); - QMetaObject::invokeMethod(this, &RFQTicketXBT::updateSubmitButton); - }; - - if (settlLeaf) { - settlLeaf->getRootPubkey(cbPubKey); - } - else { - walletsManager_->createSettlementLeaf(authAddr_, cbPubKey); - } -} - -void RFQTicketXBT::onUTXOReservationChanged(const std::string& walletId) -{ - logger_->debug("[RFQTicketXBT::onUTXOReservationChanged] walletId='{}'", walletId); - if (walletId.empty()) { - updateBalances(); - updateSubmitButton(); - return; - } - - auto xbtWallet = getSendXbtWallet(); - if (xbtWallet && (walletId == xbtWallet->walletId() || xbtWallet->getLeaf(walletId))) { - updateBalances(); - } -} - -void RFQTicketXBT::setSubmitRFQ(RFQTicketXBT::SubmitRFQCb submitRFQCb) -{ - submitRFQCb_ = std::move(submitRFQCb); -} - -void RFQTicketXBT::setCancelRFQ(RFQTicketXBT::CancelRFQCb cb) -{ - cancelRFQCb_ = std::move(cb); -} - -bs::Address RFQTicketXBT::recvXbtAddressIfSet() const -{ - const auto index = ui_->receivingAddressComboBox->currentIndex(); - if (index < 0) { - SPDLOG_LOGGER_ERROR(logger_, "invalid address index"); - return bs::Address(); - } - - if (index == 0) { - // Automatic address generation - return bs::Address(); - } - - bs::Address address; - const auto &addressStr = ui_->receivingAddressComboBox->currentText().toStdString(); - try { - address = bs::Address::fromAddressString(addressStr); - } catch (const std::exception &e) { - SPDLOG_LOGGER_ERROR(logger_, "can't parse address '{}': {}", addressStr, e.what()); - return address; - } - - // Sanity checks - auto recvWallet = getRecvXbtWallet(); - if (!recvWallet) { - SPDLOG_LOGGER_ERROR(logger_, "recv XBT wallet is not set"); - return bs::Address(); - } - auto wallet = walletsManager_->getWalletByAddress(address); - if (!wallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find receiving wallet for address {}", address.display()); - return bs::Address(); - } - auto hdWallet = walletsManager_->getHDRootForLeaf(wallet->walletId()); - if (!hdWallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find HD wallet for receiving wallet {}", wallet->walletId()); - return bs::Address(); - } - if (hdWallet != recvWallet) { - SPDLOG_LOGGER_ERROR(logger_, "receiving HD wallet {} does not contain waller {}", hdWallet->walletId(), wallet->walletId()); - return bs::Address(); - } - - return address; -} - -bool RFQTicketXBT::checkBalance(double qty) const -{ - const auto balance = getBalanceInfo(); - if (getSelectedSide() == bs::network::Side::Buy) { - if (currentGroupType_ == ProductGroupType::CCGroupType) { - return balance.amount >= getXbtReservationAmountForCc(qty, getOfferPrice()).GetValueBitcoin(); - } - return (balance.amount > 0); - } - else { - return (qty <= balance.amount); - } -} - -bool RFQTicketXBT::checkAuthAddr(double qty) const -{ - if (!ui_->authenticationAddressComboBox->isVisible()) { - return true; - } - else if (ui_->authenticationAddressComboBox->count() == 0) { - return false; - } - - if (authAddressManager_->GetState(authAddr_) == AuthAddressManager::AuthAddressState::Verified) { - return true; - } - - const auto& tradeSettings = authAddressManager_->tradeSettings(); - if (!tradeSettings) { - return false; - } - - return true; -} - -void RFQTicketXBT::updateSubmitButton() -{ - ui_->pushButtonSubmit->setEnabled(false); - - if (!assetManager_) { - return; - } - - if (currentGroupType_ != ProductGroupType::FXGroupType) { - if (signingContainer_) { - if (signingContainer_->isOffline()) { - showHelp(tr("Signer is offline - settlement will not be possible")); - return; - } - else { - clearHelp(); - } - } - - if (getProductToSpend() == UiUtils::XbtCurrency && !getSendXbtWallet()) { - return; - } - - if (getProductToRecv() == UiUtils::XbtCurrency && !getRecvXbtWallet()) { - return; - } - - if (currentGroupType_ == ProductGroupType::CCGroupType) { - auto ccWallet = getCCWallet(getProduct().toStdString()); - if (!ccWallet) { - return; - } - } - } - - const double qty = getQuantity(); - const bool isBalanceOk = checkBalance(qty); - const bool isAuthOk = checkAuthAddr(qty); - - if (!isBalanceOk || !isAuthOk) { - ui_->labelBalanceValue->setFont(invalidBalanceFont_); - return; - } - ui_->labelBalanceValue->setFont(QFont()); - - if (qFuzzyIsNull(qty)) { - return; - } - - if ((currentGroupType_ == ProductGroupType::XBTGroupType) && authKey().empty()) { - return; - } - - showHelp({}); - ui_->pushButtonSubmit->setEnabled(true); -} - -std::string RFQTicketXBT::mkRFQkey(const bs::network::RFQ &rfq) -{ - return rfq.security + "_" + rfq.product + "_" + std::to_string(rfq.side); -} - -void RFQTicketXBT::putRFQ(const bs::network::RFQ &rfq) -{ - rfqMap_[mkRFQkey(rfq)] = rfq.quantity; -} - -bool RFQTicketXBT::existsRFQ(const bs::network::RFQ &rfq) -{ - const auto rfqIt = rfqMap_.find(mkRFQkey(rfq)); - if (rfqIt == rfqMap_.end()) { - return false; - } - return qFuzzyCompare(rfq.quantity, rfqIt->second); -} - -bool RFQTicketXBT::eventFilter(QObject *watched, QEvent *evt) -{ - if (evt->type() == QEvent::KeyPress) { - auto keyID = static_cast(evt)->key(); - if (ui_->pushButtonSubmit->isEnabled() && ((keyID == Qt::Key_Return) || (keyID == Qt::Key_Enter))) { - submitButtonClicked(); - } - } - return QWidget::eventFilter(watched, evt); -} - -double RFQTicketXBT::getQuantity() const -{ - const CustomDoubleValidator *validator = dynamic_cast(ui_->lineEditAmount->validator()); - if (validator == nullptr) { - return 0; - } - return validator->GetValue(ui_->lineEditAmount->text()); -} - -double RFQTicketXBT::getOfferPrice() const -{ - const CustomDoubleValidator *validator = dynamic_cast(ui_->lineEditAmount->validator()); - if (validator == nullptr) { - return 0; - } - return validator->GetValue(currentOfferPrice_); -} - -void RFQTicketXBT::submitButtonClicked() -{ - if (!preSubmitCheck()) { - return; - } - - auto rfq = std::make_shared(); - rfq->side = getSelectedSide(); - - rfq->security = ui_->labelSecurityId->text().toStdString(); - rfq->product = getProduct().toStdString(); - - if (rfq->security.empty() || rfq->product.empty()) { - return; - } - - saveLastSideSelection(rfq->product, rfq->security, getSelectedSide()); - - rfq->quantity = getQuantity(); - if (qFuzzyIsNull(rfq->quantity)) { - return; - } - - if (currentGroupType_ == ProductGroupType::XBTGroupType) { - auto minXbtAmount = bs::tradeutils::minXbtAmount(utxoReservationManager_->feeRatePb()); - if (expectedXbtAmountMin().GetValue() < minXbtAmount.GetValue()) { - auto minAmountStr = UiUtils::displayQuantity(minXbtAmount.GetValueBitcoin(), bs::network::XbtCurrency); - BSMessageBox(BSMessageBox::critical, tr("Spot XBT"), tr("Invalid amount") - , tr("Expected bitcoin amount will not cover network fee.\nMinimum amount: %1").arg(minAmountStr), this).exec(); - return; - } - } - - switch (currentGroupType_) { - case ProductGroupType::GroupNotSelected: - rfq->assetType = bs::network::Asset::Undefined; - break; - case ProductGroupType::FXGroupType: - rfq->assetType = bs::network::Asset::SpotFX; - break; - case ProductGroupType::XBTGroupType: - rfq->assetType = bs::network::Asset::SpotXBT; - break; - case ProductGroupType::CCGroupType: - rfq->assetType = bs::network::Asset::PrivateMarket; - break; - } - - const auto &rfqId = CryptoPRNG::generateRandom(8).toHexStr(); - pendingRFQs_[rfqId] = rfq; - - if (!existsRFQ(*rfq)) { - putRFQ(*rfq); - sendRFQ(rfqId); - } -} - -void RFQTicketXBT::onSendRFQ(const std::string &id, const QString &symbol, double amount, bool buy) -{ - auto rfq = std::make_shared(); - rfq->side = buy ? bs::network::Side::Buy : bs::network::Side::Sell; - - const CurrencyPair cp(symbol.toStdString()); - rfq->security = symbol.toStdString(); - rfq->product = cp.NumCurrency(); - rfq->quantity = amount; - rfq->assetType = assetManager_->GetAssetTypeForSecurity(rfq->security); - - if (rfq->security.empty() || rfq->product.empty() || qFuzzyIsNull(rfq->quantity)) { - return; - } - - pendingRFQs_[id] = rfq; - - if (rfq->assetType == bs::network::Asset::SpotXBT) { - authAddr_ = authAddressManager_->getDefault(); - if (authAddr_.empty()) { - deferredRFQs_.push_back(id); - return; - } - if (!walletsManager_->getSettlementLeaf(authAddr_)) { - deferredRFQs_.push_back(id); - return; - } - } - - sendRFQ(id); -} - -void RFQTicketXBT::sendRFQ(const std::string &id) -{ - const auto &itRFQ = pendingRFQs_.find(id); - if (itRFQ == pendingRFQs_.end()) { - logger_->error("[RFQTicketXBT::onSendRFQ] RFQ with id {} not found", id); - return; - } - - logger_->debug("[RFQTicketXBT::sendRFQ] sending RFQ {}", id); - - auto rfq = itRFQ->second; - - if (rfq->requestId.empty()) { - rfq->requestId = "blocksettle:" + id; - } - - if (rfq->assetType == bs::network::Asset::SpotXBT) { - rfq->requestorAuthPublicKey = authKey(); - if (rfq->requestorAuthPublicKey.empty()) { - logger_->debug("[RFQTicketXBT::onSendRFQ] auth key is empty for {}", authAddr_.display()); - deferredRFQs_.push_back(id); - } - else { - reserveBestUtxoSetAndSubmit(id, rfq); - } - return; - } - else if (rfq->assetType == bs::network::Asset::PrivateMarket) { - auto ccWallet = getCCWallet(rfq->product); - if (!ccWallet) { - SPDLOG_LOGGER_ERROR(logger_, "can't find CC wallet for {}", rfq->product); - return; - } - - if (rfq->side == bs::network::Side::Sell) { - // Sell - const auto &recvXbtAddressCb = [this, id, rfq, ccWallet] - (const bs::Address &recvXbtAddr) - { - rfq->receiptAddress = recvXbtAddr.display(); - const uint64_t spendVal = rfq->quantity * assetManager_->getCCLotSize(rfq->product); - if (!ccWallet) { - SPDLOG_LOGGER_ERROR(logger_, "ccWallet is not set"); - return; - } - - const auto ccInputsCb = [this, id, spendVal, rfq, ccWallet] - (const std::vector &ccInputs) mutable - { - QMetaObject::invokeMethod(this, [this, id, spendVal, rfq, ccInputs, ccWallet] { - uint64_t inputVal = 0; - for (const auto &input : ccInputs) { - inputVal += input.getValue(); - } - if (inputVal < spendVal) { - // This should not normally happen! - SPDLOG_LOGGER_ERROR(logger_, "insufficient input amount: {}, expected: {}, requestId: {}", inputVal, spendVal, rfq->requestId); - BSMessageBox(BSMessageBox::critical, tr("RFQ not sent") - , tr("Insufficient input amount")).exec(); - return; - } - - const auto cbAddr = [this, spendVal, id, rfq, ccInputs, ccWallet](const bs::Address &addr) - { - try { - const auto txReq = ccWallet->createPartialTXRequest( - spendVal, ccInputs - , { addr, RECIP_GROUP_CHANG_1 } //change to group 1 (cc group) - , 0, {}, {} - /* - This cc is created without recipients. Set the assumed recipient count - to 1 so the coin selection algo can run, otherwise all presented inputs - will be selected, which is wasteful. - - The assumed recipient count isn't relevant to the fee calculation on - the cc side of the tx since only the xbt side covers network fees. - */ - , 1); - - auto resolveCB = [this, id, rfq] - (bs::error::ErrorCode result, const Codec_SignerState::SignerState &state) - { - if (result != bs::error::ErrorCode::NoError) { - std::stringstream ss; - ss << "failed to resolve CC half signer with error code: " << (int)result; - throw std::runtime_error(ss.str()); - } - - bs::core::wallet::TXSignRequest req; - req.armorySigner_.deserializeState(state); - auto reservationToken = utxoReservationManager_->makeNewReservation( - req.getInputs(nullptr), rfq->requestId); - - rfq->coinTxInput = BinaryData::fromString(state.SerializeAsString()).toHexStr(); - submitRFQCb_(id, *rfq, std::move(reservationToken)); - }; - signingContainer_->resolvePublicSpenders(txReq, resolveCB); - } - catch (const std::exception &e) { - BSMessageBox(BSMessageBox::critical, tr("RFQ Failure") - , QString::fromLatin1(e.what()), this).exec(); - return; - } - }; - if (inputVal == spendVal) { - cbAddr({}); - } - else { - ccWallet->getNewChangeAddress(cbAddr); - } - }); - }; - bool result = ccWallet->getSpendableTxOutList(ccInputsCb, spendVal, true); - if (!result) { - SPDLOG_LOGGER_ERROR(logger_, "can't spendable TX list"); - } - }; - - auto recvXbtAddrIfSet = recvXbtAddressIfSet(); - if (recvXbtAddrIfSet.isValid()) { - recvXbtAddressCb(recvXbtAddrIfSet); - } - else { - auto recvXbtWallet = getRecvXbtWallet(); - if (!recvXbtWallet) { - SPDLOG_LOGGER_ERROR(logger_, "recv XBT wallet is not set"); - return; - } - auto leaves = recvXbtWallet->getGroup(recvXbtWallet->getXBTGroupType())->getLeaves(); - if (leaves.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "can't find XBT leaves"); - return; - } - // BST-2474: All addresses related to trading, not just change addresses, should use internal addresses - leaves.front()->getNewIntAddress(recvXbtAddressCb); - } - return; - } - - // Buy - auto cbRecvAddr = [this, id, rfq](const bs::Address &recvAddr) { - rfq->receiptAddress = recvAddr.display(); - reserveBestUtxoSetAndSubmit(id, rfq); - }; - // BST-2474: All addresses related to trading, not just change addresses, should use internal addresses. - // This has no effect as CC wallets have only external addresses. - // But CCLeaf::getSpendableTxOutList is require only 1 conf so it's OK. - ccWallet->getNewIntAddress(cbRecvAddr); - return; - } - - submitRFQCb_(id, *rfq, bs::UtxoReservationToken{}); -} - -void RFQTicketXBT::onCancelRFQ(const std::string &id) -{ - const auto &itRFQ = pendingRFQs_.find(id); - if (itRFQ == pendingRFQs_.end()) { - logger_->error("[RFQTicketXBT::onCancelRFQ] failed to find RFQ {}", id); - return; - } - logger_->debug("[RFQTicketXBT::onCancelRFQ] cancelling RFQ {}", id); - if (cancelRFQCb_) { - cancelRFQCb_(id); - } - pendingRFQs_.erase(itRFQ); -} - -void RFQTicketXBT::onMDUpdate(bs::network::Asset::Type, const QString &security, bs::network::MDFields mdFields) -{ - auto &mdInfo = mdInfo_[security.toStdString()]; - mdInfo.merge(bs::network::MDField::get(mdFields)); -} - -QPushButton* RFQTicketXBT::submitButton() const -{ - return ui_->pushButtonSubmit; -} - -QLineEdit* RFQTicketXBT::lineEditAmount() const -{ - return ui_->lineEditAmount; -} - -QPushButton* RFQTicketXBT::buyButton() const -{ - return ui_->pushButtonBuy; -} - -QPushButton* RFQTicketXBT::sellButton() const -{ - return ui_->pushButtonSell; -} - -QPushButton* RFQTicketXBT::numCcyButton() const -{ - return ui_->pushButtonNumCcy; -} - -QPushButton* RFQTicketXBT::denomCcyButton() const -{ - return ui_->pushButtonDenomCcy; -} - -bs::Address RFQTicketXBT::selectedAuthAddress() const -{ - return authAddr_; -} - -void RFQTicketXBT::saveLastSideSelection(const std::string& product, const std::string& security, bs::network::Side::Type sideIndex) -{ - lastSideSelection_[product + security] = sideIndex; -} - -bs::network::Side::Type RFQTicketXBT::getLastSideSelection(const std::string& product, const std::string& security) -{ - auto it = lastSideSelection_.find(product + security); - if (it == lastSideSelection_.end()) { - return bs::network::Side::Sell; - } - - return it->second; -} - -void RFQTicketXBT::disablePanel() -{ - resetTicket(); - - // show help - showHelp(tr("Login in order to send RFQ")); -} - -std::shared_ptr RFQTicketXBT::xbtWallet() const -{ - if (getProductToSpend() == UiUtils::XbtCurrency) { - return getSendXbtWallet(); - } - if (getProductToRecv() == UiUtils::XbtCurrency) { - return getRecvXbtWallet(); - } - return nullptr; -} - -UiUtils::WalletsTypes RFQTicketXBT::xbtWalletType() const -{ - QComboBox* combobox = nullptr; - if (getProductToSpend() == UiUtils::XbtCurrency) { - combobox = ui_->comboBoxXBTWalletsSend; - } - if (getProductToRecv() == UiUtils::XbtCurrency) { - combobox = ui_->comboBoxXBTWalletsRecv; - } - - if (!combobox) { - return UiUtils::None; - } - - return UiUtils::getSelectedWalletType(combobox); -} - -void RFQTicketXBT::onParentAboutToHide() -{ - fixedXbtInputs_ = {}; -} - -void RFQTicketXBT::enablePanel() -{ - resetTicket(); - clearHelp(); -} - -void RFQTicketXBT::HideRFQControls() -{ - ui_->groupBoxSettlementInputs->setVisible(false); - - // amount and balance controls - ui_->lineBeforeProduct->setVisible(false); - ui_->verticalWidgetSelectedProduct->setVisible(false); - - ui_->lineBeforeBalance->setVisible(false); - ui_->balanceLayout->setVisible(false); -} - -void RFQTicketXBT::showHelp(const QString& helpText) -{ - ui_->helpLabel->setText(helpText); - ui_->helpLabel->setVisible(true); -} - -void RFQTicketXBT::clearHelp() -{ - ui_->helpLabel->setVisible(false); -} - -void RFQTicketXBT::initProductGroupMap() -{ - groupNameToType_.emplace(bs::network::Asset::toString(bs::network::Asset::PrivateMarket) - , ProductGroupType::CCGroupType); - groupNameToType_.emplace(bs::network::Asset::toString(bs::network::Asset::SpotXBT) - , ProductGroupType::XBTGroupType); - groupNameToType_.emplace(bs::network::Asset::toString(bs::network::Asset::SpotFX) - , ProductGroupType::FXGroupType); -} - -RFQTicketXBT::ProductGroupType RFQTicketXBT::getProductGroupType(const QString& productGroup) -{ - auto it = groupNameToType_.find(productGroup.toStdString()); - if (it != groupNameToType_.end()) { - return it->second; - } - - return ProductGroupType::GroupNotSelected; -} - -void RFQTicketXBT::onMaxClicked() -{ - auto balanceInfo = getBalanceInfo(); - - switch(balanceInfo.productType) { - case ProductGroupType::XBTGroupType: - { - const auto xbtWallet = getSendXbtWallet(); - if (!xbtWallet) { - ui_->lineEditAmount->clear(); - updateSubmitButton(); - return; - } - - std::vector utxos; - if (!fixedXbtInputs_.inputs.empty()) { - utxos.reserve(fixedXbtInputs_.inputs.size()); - for (const auto &utxoPair : fixedXbtInputs_.inputs) { - utxos.push_back(utxoPair.first); - } - } - else { - if (!xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWalletsSend); - utxos = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcRequestor); - } - else { - utxos = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet->walletId(), bs::UTXOReservationManager::kIncludeZcRequestor); - } - } - - auto feeCb = [this, utxos = std::move(utxos)](float fee) { - QMetaObject::invokeMethod(this, [this, fee, utxos = std::move(utxos)]{ - float feePerByteArmory = ArmoryConnection::toFeePerByte(fee); - auto feePerByte = std::max(feePerByteArmory, utxoReservationManager_->feeRatePb()); - uint64_t total = 0; - for (const auto &utxo : utxos) { - total += utxo.getValue(); - } - const uint64_t fee = bs::tradeutils::estimatePayinFeeWithoutChange(utxos, feePerByte); - const double spendableQuantity = std::max(0.0, (total - fee) / BTCNumericTypes::BalanceDivider); - ui_->lineEditAmount->setText(UiUtils::displayAmount(spendableQuantity)); - updateSubmitButton(); - }); - }; - armory_->estimateFee(bs::tradeutils::feeTargetBlockCount(), feeCb); - - return; - } - case ProductGroupType::CCGroupType: { - ui_->lineEditAmount->setText(UiUtils::displayCCAmount(qMax(balanceInfo.amount, 0))); - break; - } - case ProductGroupType::FXGroupType: { - ui_->lineEditAmount->setText(UiUtils::displayCurrencyAmount(qMax(balanceInfo.amount, 0))); - break; - } - } - - updateSubmitButton(); -} - -void RFQTicketXBT::onAmountEdited(const QString &) -{ - updateSubmitButton(); -} - -void RFQTicketXBT::SetCurrentIndicativePrices(const QString& bidPrice, const QString& offerPrice) -{ - if (bidPrice.isEmpty()) { - currentBidPrice_ = kEmptyInformationalLabelText; - } else { - currentBidPrice_ = bidPrice; - } - - if (offerPrice.isEmpty()) { - currentOfferPrice_ = kEmptyInformationalLabelText; - } else { - currentOfferPrice_ = offerPrice; - } -} - -void RFQTicketXBT::updateIndicativePrice() -{ - auto selectedSide = getSelectedSide(); - if (selectedSide != bs::network::Side::Undefined) { - int numCcySelected = ui_->pushButtonNumCcy->isChecked(); - bool isSell = numCcySelected ^ (selectedSide == bs::network::Side::Buy); - - if (isSell) { - ui_->labelIndicativePrice->setText(currentBidPrice_); - } else { - ui_->labelIndicativePrice->setText(currentOfferPrice_); - } - } else { - ui_->labelIndicativePrice->setText(kEmptyInformationalLabelText); - } -} - -double RFQTicketXBT::getIndicativePrice() const -{ - const auto &mdIt = mdInfo_.find(ui_->labelSecurityId->text().toStdString()); - if (mdIt == mdInfo_.end()) { - return false; - } - - auto selectedSide = getSelectedSide(); - if (selectedSide == bs::network::Side::Undefined) { - return .0; - } - bool numCcySelected = ui_->pushButtonNumCcy->isChecked(); - bool isSell = selectedSide == bs::network::Side::Buy - ? !numCcySelected - : numCcySelected; - - if (isSell) { - return mdIt->second.bidPrice; - } - else { - return mdIt->second.askPrice; - } -} - -void RFQTicketXBT::onNumCcySelected() -{ - ui_->pushButtonNumCcy->setChecked(true); - ui_->pushButtonDenomCcy->setChecked(false); - - currentProduct_ = ui_->pushButtonNumCcy->text(); - contraProduct_ = ui_->pushButtonDenomCcy->text(); - - productSelectionChanged(); -} - -void RFQTicketXBT::onDenomCcySelected() -{ - ui_->pushButtonNumCcy->setChecked(false); - ui_->pushButtonDenomCcy->setChecked(true); - - currentProduct_ = ui_->pushButtonDenomCcy->text(); - contraProduct_ = ui_->pushButtonNumCcy->text(); - - productSelectionChanged(); -} - -void RFQTicketXBT::onSellSelected() -{ - ui_->pushButtonSell->setChecked(true); - ui_->pushButtonBuy->setChecked(false); - productSelectionChanged(); -} - -void RFQTicketXBT::onBuySelected() -{ - ui_->pushButtonSell->setChecked(false); - ui_->pushButtonBuy->setChecked(true); - productSelectionChanged(); -} - -void RFQTicketXBT::productSelectionChanged() -{ - rfqMap_.clear(); - - ui_->pushButtonSubmit->show(); - ui_->pushButtonCreateWallet->hide(); - - ui_->lineEditAmount->setValidator(nullptr); - ui_->lineEditAmount->setEnabled(false); - ui_->lineEditAmount->clear(); - - ui_->toolButtonMax->setEnabled(true); - ui_->toolButtonXBTInputsSend->setEnabled(true); - - fixedXbtInputs_ = {}; - - if (currentGroupType_ == ProductGroupType::FXGroupType) { - ui_->lineEditAmount->setValidator(fxAmountValidator_); - ui_->lineEditAmount->setEnabled(true); - } else { - bool canTradeXBT = (armory_->state() == ArmoryState::Ready) - && signingContainer_ - && !signingContainer_->isOffline(); - - ui_->lineEditAmount->setEnabled(canTradeXBT); - ui_->toolButtonMax->setEnabled(canTradeXBT); - ui_->toolButtonXBTInputsSend->setEnabled(canTradeXBT); - - if (!canTradeXBT) { - ui_->labelBalanceValue->setText(tr("---")); - return; - } - - if (currentGroupType_ == ProductGroupType::CCGroupType) { - ui_->lineEditAmount->setValidator(ccAmountValidator_); - - const auto &product = getProduct(); - const auto ccWallet = getCCWallet(product.toStdString()); - if (!ccWallet) { - if (signingContainer_ && !signingContainer_->isOffline() && walletsManager_) { - ui_->pushButtonSubmit->hide(); - ui_->pushButtonCreateWallet->show(); - ui_->pushButtonCreateWallet->setEnabled(true); - ui_->pushButtonCreateWallet->setText(tr("Create %1 wallet").arg(product)); - } else { - BSMessageBox errorMessage(BSMessageBox::critical, tr("Signer not connected") - , tr("Could not create CC subwallet.") - , this); - errorMessage.exec(); - showHelp(tr("CC wallet missing")); - } - } - } else { - if (currentProduct_ == UiUtils::XbtCurrency) { - ui_->lineEditAmount->setValidator(xbtAmountValidator_); - } else { - ui_->lineEditAmount->setValidator(fxAmountValidator_); - } - } - } - - ui_->lineEditAmount->setFocus(); - - updatePanel(); - - fillRecvAddresses(); -} - -std::shared_ptr RFQTicketXBT::getSendXbtWallet() const -{ - if (!walletsManager_) { - return nullptr; - } - auto wallet = walletsManager_->getHDWalletById(ui_->comboBoxXBTWalletsSend-> - currentData(UiUtils::WalletIdRole).toString().toStdString()); - if (!wallet) { - const auto &defaultWallet = walletsManager_->getDefaultWallet(); - if (defaultWallet) { - wallet = walletsManager_->getHDRootForLeaf(defaultWallet->walletId()); - } - } - return wallet; -} - -std::shared_ptr RFQTicketXBT::getRecvXbtWallet() const -{ - if (!walletsManager_) { - return nullptr; - } - auto wallet = walletsManager_->getHDWalletById(ui_->comboBoxXBTWalletsRecv-> - currentData(UiUtils::WalletIdRole).toString().toStdString()); - if (!wallet && walletsManager_->getDefaultWallet()) { - wallet = walletsManager_->getHDRootForLeaf(walletsManager_->getDefaultWallet()->walletId()); - } - return wallet; -} - -bs::XBTAmount RFQTicketXBT::getXbtBalance() const -{ - const auto &fixedInputs = fixedXbtInputs_.inputs; - if (!fixedXbtInputs_.inputs.empty()) { - uint64_t sum = 0; - for (const auto &utxo : fixedInputs) { - sum += utxo.first.getValue(); - } - return bs::XBTAmount(sum); - } - - auto xbtWallet = getSendXbtWallet(); - if (!xbtWallet) { - return bs::XBTAmount(0.0); - } - - if (!xbtWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(ui_->comboBoxXBTWalletsSend); - return bs::XBTAmount(utxoReservationManager_->getAvailableXbtUtxoSum( - xbtWallet->walletId(), purpose, bs::UTXOReservationManager::kIncludeZcRequestor)); - } - else { - return bs::XBTAmount(utxoReservationManager_->getAvailableXbtUtxoSum( - xbtWallet->walletId(), bs::UTXOReservationManager::kIncludeZcRequestor)); - } -} - -QString RFQTicketXBT::getProductToSpend() const -{ - if (getSelectedSide() == bs::network::Side::Sell) { - return currentProduct_; - } else { - return contraProduct_; - } -} - -QString RFQTicketXBT::getProductToRecv() const -{ - if (getSelectedSide() == bs::network::Side::Buy) { - return currentProduct_; - } else { - return contraProduct_; - } -} - -bs::XBTAmount RFQTicketXBT::expectedXbtAmountMin() const -{ - if (currentProduct_ == UiUtils::XbtCurrency) { - return bs::XBTAmount(getQuantity()); - } - const auto &tradeSettings = authAddressManager_->tradeSettings(); - auto maxPrice = getIndicativePrice() * (1 + (tradeSettings->xbtPriceBand / 100)); - return bs::XBTAmount(getQuantity() / maxPrice); -} - -bs::XBTAmount RFQTicketXBT::getXbtReservationAmountForCc(double quantity, double offerPrice) const -{ - return bs::XBTAmount(quantity * offerPrice * bs::tradeutils::reservationQuantityMultiplier()); -} - -void RFQTicketXBT::reserveBestUtxoSetAndSubmit(const std::string &id - , const std::shared_ptr& rfq) -{ - // Skip UTXO reservations amount checks for buy fiat requests as reserved XBT amount is 20% more than expected from current price. - auto checkAmount = (rfq->side == bs::network::Side::Sell && rfq->product != bs::network::XbtCurrency) ? - bs::UTXOReservationManager::CheckAmount::Enabled : bs::UTXOReservationManager::CheckAmount::Disabled; - - const auto &submitRFQWrapper = [rfqTicket = QPointer(this), id, rfq] - { - if (!rfqTicket) { - return; - } - rfqTicket->submitRFQCb_(id, *rfq, std::move(rfqTicket->fixedXbtInputs_.utxoRes)); - }; - auto getWalletAndReserve = [rfqTicket = QPointer(this), submitRFQWrapper, checkAmount] - (BTCNumericTypes::satoshi_type amount, bool partial) - { - auto cbBestUtxoSet = [rfqTicket, submitRFQWrapper](bs::FixedXbtInputs&& fixedXbt) { - if (!rfqTicket) { - return; - } - rfqTicket->fixedXbtInputs_ = std::move(fixedXbt); - submitRFQWrapper(); - }; - - auto hdWallet = rfqTicket->getSendXbtWallet(); - if (!hdWallet->canMixLeaves()) { - auto purpose = UiUtils::getSelectedHwPurpose(rfqTicket->ui_->comboBoxXBTWalletsSend); - rfqTicket->utxoReservationManager_->reserveBestXbtUtxoSet( - hdWallet->walletId(), purpose, amount, - partial, std::move(cbBestUtxoSet), true, checkAmount); - } - else { - rfqTicket->utxoReservationManager_->reserveBestXbtUtxoSet( - hdWallet->walletId(), amount, - partial, std::move(cbBestUtxoSet), true, checkAmount, bs::UTXOReservationManager::kIncludeZcRequestor); - } - }; - - if (rfq->assetType == bs::network::Asset::PrivateMarket - && rfq->side == bs::network::Side::Buy) { - auto maxXbtQuantity = getXbtReservationAmountForCc(rfq->quantity, getOfferPrice()).GetValue(); - getWalletAndReserve(maxXbtQuantity, true); - return; - } - - if ((rfq->side == bs::network::Side::Sell && rfq->product != bs::network::XbtCurrency) || - (rfq->side == bs::network::Side::Buy && rfq->product == bs::network::XbtCurrency)) { - submitRFQWrapper(); - return; // Nothing to reserve - } - - if (!fixedXbtInputs_.inputs.empty()) { - submitRFQWrapper(); - return; // already reserved by user - } - - auto quantity = bs::XBTAmount(rfq->quantity).GetValue(); - if (rfq->side == bs::network::Side::Buy) { - if (rfq->assetType == bs::network::Asset::PrivateMarket) { - quantity *= bs::XBTAmount(getOfferPrice()).GetValue(); - } - else if (rfq->assetType == bs::network::Asset::SpotXBT) { - quantity /= getOfferPrice(); - } - } - - const bool partial = rfq->assetType == bs::network::Asset::PrivateMarket; - getWalletAndReserve(quantity, partial); -} - -void RFQTicketXBT::onCreateWalletClicked() -{ - ui_->pushButtonCreateWallet->setEnabled(false); - - if (!walletsManager_->CreateCCLeaf(getProduct().toStdString())) { - showHelp(tr("Create CC wallet request failed")); - } -} diff --git a/BlockSettleUILib/Trading/RFQTicketXBT.h b/BlockSettleUILib/Trading/RFQTicketXBT.h deleted file mode 100644 index 13cb8de66..000000000 --- a/BlockSettleUILib/Trading/RFQTicketXBT.h +++ /dev/null @@ -1,275 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __RFQ_TICKET_XBT_H__ -#define __RFQ_TICKET_XBT_H__ - -#include -#include - -#include -#include -#include -#include - -#include "BSErrorCode.h" -#include "CommonTypes.h" -#include "UtxoReservationToken.h" -#include "XBTAmount.h" -#include "UiUtils.h" - -QT_BEGIN_NAMESPACE -class QPushButton; -class QLineEdit; -QT_END_NAMESPACE - -namespace spdlog { - class logger; -} -namespace Ui { - class RFQTicketXBT; -} -namespace bs { - namespace sync { - namespace hd { - class Leaf; - class Wallet; - } - class Wallet; - class WalletsManager; - } - class UTXOReservationManager; -} -class ArmoryConnection; -class AssetManager; -class AuthAddressManager; -class CCAmountValidator; -class FXAmountValidator; -class QuoteProvider; -class SelectedTransactionInputs; -class SignContainer; -class XbtAmountValidator; - - -class RFQTicketXBT : public QWidget -{ -Q_OBJECT - -public: - RFQTicketXBT(QWidget* parent = nullptr); - ~RFQTicketXBT() override; - - void init(const std::shared_ptr &logger - , const std::shared_ptr & - , const std::shared_ptr &assetManager - , const std::shared_ptr "eProvider - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); - void setWalletsManager(const std::shared_ptr &); - - void resetTicket(); - - bs::FixedXbtInputs fixedXbtInputs(); - - QPushButton* submitButton() const; - QLineEdit* lineEditAmount() const; - QPushButton* buyButton() const; - QPushButton* sellButton() const; - QPushButton* numCcyButton() const; - QPushButton* denomCcyButton() const; - - bs::Address selectedAuthAddress() const; - // returns empty address if automatic selected - bs::Address recvXbtAddressIfSet() const; - - using SubmitRFQCb = std::function; - void setSubmitRFQ(SubmitRFQCb); - using CancelRFQCb = std::function; - void setCancelRFQ(CancelRFQCb); - - std::shared_ptr xbtWallet() const; - UiUtils::WalletsTypes xbtWalletType() const; - - void onParentAboutToHide(); - -public slots: - void SetProductAndSide(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice, bs::network::Side::Type side); - void setSecurityId(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice); - void setSecurityBuy(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice); - void setSecuritySell(const QString& productGroup, const QString& currencyPair - , const QString& bidPrice, const QString& offerPrice); - - void enablePanel(); - void disablePanel(); - - void onSendRFQ(const std::string &id, const QString &symbol, double amount, bool buy); - void onCancelRFQ(const std::string &id); - - void onMDUpdate(bs::network::Asset::Type, const QString &security, bs::network::MDFields); - -private slots: - void updateBalances(); - void onSignerReady(); - void walletsLoaded(); - - void onNumCcySelected(); - void onDenomCcySelected(); - - void onSellSelected(); - void onBuySelected(); - - void showCoinControl(); - void walletSelectedRecv(int index); - void walletSelectedSend(int index); - - void updateSubmitButton(); - void submitButtonClicked(); - - void onHDLeafCreated(const std::string& ccName); - void onCreateHDWalletError(const std::string& ccName, bs::error::ErrorCode result); - - void onMaxClicked(); - void onAmountEdited(const QString &); - - void onCreateWalletClicked(); - - void onAuthAddrChanged(int); - void onSettlLeavesLoaded(unsigned int); - - void onUTXOReservationChanged(const std::string& walletId); - -protected: - bool eventFilter(QObject *watched, QEvent *evt) override; - -private: - enum class ProductGroupType - { - GroupNotSelected, - FXGroupType, - XBTGroupType, - CCGroupType - }; - - struct BalanceInfoContainer - { - double amount; - QString product; - ProductGroupType productType; - }; - -private: - void showHelp(const QString& helpText); - void clearHelp(); - void sendRFQ(const std::string &id); - - void updatePanel(); - - void fillRecvAddresses(); - - bool preSubmitCheck(); - - BalanceInfoContainer getBalanceInfo() const; - QString getProduct() const; - std::shared_ptr getCCWallet(const std::string &cc) const; - bool checkBalance(double qty) const; - bool checkAuthAddr(double qty) const; - bs::network::Side::Type getSelectedSide() const; - std::string authKey() const; - - void putRFQ(const bs::network::RFQ &); - bool existsRFQ(const bs::network::RFQ &); - - static std::string mkRFQkey(const bs::network::RFQ &); - - void SetProductGroup(const QString& productGroup); - void SetCurrencyPair(const QString& currencyPair); - - void saveLastSideSelection(const std::string& product, const std::string& currencyPair, bs::network::Side::Type side); - bs::network::Side::Type getLastSideSelection(const std::string& product, const std::string& currencyPair); - - void HideRFQControls(); - - void initProductGroupMap(); - ProductGroupType getProductGroupType(const QString& productGroup); - - double getQuantity() const; - double getOfferPrice() const; - - void SetCurrentIndicativePrices(const QString& bidPrice, const QString& offerPrice); - void updateIndicativePrice(); - double getIndicativePrice() const; - - void productSelectionChanged(); - - std::shared_ptr getSendXbtWallet() const; - std::shared_ptr getRecvXbtWallet() const; - bs::XBTAmount getXbtBalance() const; - QString getProductToSpend() const; - QString getProductToRecv() const; - bs::XBTAmount expectedXbtAmountMin() const; - bs::XBTAmount getXbtReservationAmountForCc(double quantity, double offerPrice) const; - - void reserveBestUtxoSetAndSubmit(const std::string &id - , const std::shared_ptr& rfq); - -private: - std::unique_ptr ui_; - - std::shared_ptr logger_; - std::shared_ptr assetManager_; - std::shared_ptr authAddressManager_; - - std::shared_ptr walletsManager_; - std::shared_ptr signingContainer_; - std::shared_ptr armory_; - std::shared_ptr utxoReservationManager_; - - mutable bs::Address authAddr_; - mutable std::string authKey_; - - unsigned int leafCreateReqId_ = 0; - - std::unordered_map rfqMap_; - std::unordered_map> pendingRFQs_; - - std::unordered_map lastSideSelection_; - - QFont invalidBalanceFont_; - - CCAmountValidator *ccAmountValidator_{}; - FXAmountValidator *fxAmountValidator_{}; - XbtAmountValidator *xbtAmountValidator_{}; - - std::unordered_map groupNameToType_; - ProductGroupType currentGroupType_ = ProductGroupType::GroupNotSelected; - - QString currentProduct_; - QString contraProduct_; - - QString currentBidPrice_; - QString currentOfferPrice_; - - SubmitRFQCb submitRFQCb_{}; - CancelRFQCb cancelRFQCb_{}; - - bs::FixedXbtInputs fixedXbtInputs_; - - bool autoRFQenabled_{ false }; - std::vector deferredRFQs_; - - std::unordered_map mdInfo_; -}; - -#endif // __RFQ_TICKET_XBT_H__ diff --git a/BlockSettleUILib/Trading/RFQTicketXBT.ui b/BlockSettleUILib/Trading/RFQTicketXBT.ui deleted file mode 100644 index c90f52eb2..000000000 --- a/BlockSettleUILib/Trading/RFQTicketXBT.ui +++ /dev/null @@ -1,1192 +0,0 @@ - - - - RFQTicketXBT - - - - 0 - 0 - 460 - 757 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 25 - - - - true - - - - 5 - - - 5 - - - 0 - - - 0 - - - 0 - - - - - QUOTE REQUEST - - - 0 - - - true - - - - - - - - - - - 10 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - GENERAL - - - true - - - - 10 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 5 - - - 5 - - - 5 - - - 5 - - - 0 - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 16777215 - - - - Product Group - - - - - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 16777215 - - - - Security ID - - - - - - - font-weight: bold; - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 16777215 - - - - Indicative Price - - - - - - - -- - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 5 - - - 5 - - - 0 - - - 5 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Product - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 60 - 30 - - - - - 60 - 30 - - - - --- - - - true - - - true - - - - - - - - 60 - 30 - - - - - 60 - 30 - - - - --- - - - true - - - true - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 1 - - - 0 - - - 0 - - - 0 - - - - - Side - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 60 - 30 - - - - - 60 - 30 - - - - Buy - - - true - - - true - - - - - - - - 60 - 30 - - - - - 60 - 30 - - - - Sell - - - true - - - true - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Quantity - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 21 - - - - - 16777215 - 21 - - - - - - - - - - - - 100 - 21 - - - - - 100 - 21 - - - - &Max - - - - - - - - - - - - - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 3 - - - 10 - - - 0 - - - 10 - - - 10 - - - - - Balance - - - - - - - font-weight: bold; - - - xxx - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - - - - 0 - 0 - - - - SETTLEMENT DETAILS - - - true - - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Payment Wallet - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 21 - - - - - 16777215 - 21 - - - - - - - - - 100 - 21 - - - - - 100 - 21 - - - - &Inputs - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Receiving Wallet - - - - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 21 - - - - - 16777215 - 21 - - - - - - - - - - - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Receiving Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Authentication Address - - - - - - - - 0 - 0 - - - - - 0 - 20 - - - - - 16777215 - 20 - - - - - - - - - - - - - - - 0 - 0 - - - - help text - - - true - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - 0 - 0 - - - - true - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 0 - 35 - - - - - - - SUBMIT QUOTE REQUEST - - - - - - - - 0 - 35 - - - - Create Wallet - - - - - - - - - - - diff --git a/BlockSettleUILib/Trading/ReqCCSettlementContainer.cpp b/BlockSettleUILib/Trading/ReqCCSettlementContainer.cpp deleted file mode 100644 index bc6114a84..000000000 --- a/BlockSettleUILib/Trading/ReqCCSettlementContainer.cpp +++ /dev/null @@ -1,429 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ReqCCSettlementContainer.h" -#include -#include "AssetManager.h" -#include "CheckRecipSigner.h" -#include "SignContainer.h" -#include "TradesUtils.h" -#include "TransactionData.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "BSErrorCodeStrings.h" -#include "UiUtils.h" -#include "XBTAmount.h" -#include "UtxoReservationManager.h" - -using namespace bs::sync; - -ReqCCSettlementContainer::ReqCCSettlementContainer(const std::shared_ptr &logger - , const std::shared_ptr &container - , const std::shared_ptr &armory - , const std::shared_ptr &assetMgr - , const std::shared_ptr &walletsMgr - , const bs::network::RFQ &rfq - , const bs::network::Quote "e - , const std::shared_ptr &xbtWallet - , const std::map &manualXbtInputs - , const std::shared_ptr &utxoReservationManager - , std::unique_ptr walletPurpose - , bs::UtxoReservationToken utxoRes - , bool expandTxDialogInfo) - : bs::SettlementContainer(std::move(utxoRes), std::move(walletPurpose), expandTxDialogInfo) - , logger_(logger) - , signingContainer_(container) - , xbtWallet_(xbtWallet) - , assetMgr_(assetMgr) - , walletsMgr_(walletsMgr) - , rfq_(rfq) - , quote_(quote) - , genAddress_(assetMgr_->getCCGenesisAddr(product())) - , dealerAddress_(quote_.dealerAuthPublicKey) - , signer_(armory) - , lotSize_(assetMgr_->getCCLotSize(product())) - , manualXbtInputs_(manualXbtInputs) - , utxoReservationManager_(utxoReservationManager) - , armory_(armory) -{ - if (!xbtWallet_) { - throw std::logic_error("invalid hd wallet"); - } - - auto xbtGroup = xbtWallet_->getGroup(xbtWallet_->getXBTGroupType()); - if (!xbtGroup) { - throw std::invalid_argument(fmt::format("can't find XBT group in {}", xbtWallet_->walletId())); - } - auto xbtLeaves = xbtGroup->getLeaves(); - xbtLeaves_.insert(xbtLeaves_.end(), xbtLeaves.begin(), xbtLeaves.end()); - if (xbtLeaves_.empty()) { - throw std::invalid_argument(fmt::format("empty XBT group in {}", xbtWallet_->walletId())); - } - - if (lotSize_ == 0) { - throw std::runtime_error("invalid lot size"); - } - - ccWallet_ = walletsMgr->getCCWallet(rfq.product); - if (!ccWallet_) { - throw std::logic_error("can't find CC wallet"); - } - - connect(signingContainer_.get(), &SignContainer::QWalletInfo, this, &ReqCCSettlementContainer::onWalletInfo); - connect(this, &ReqCCSettlementContainer::genAddressVerified, this - , &ReqCCSettlementContainer::onGenAddressVerified, Qt::QueuedConnection); - - const auto &rootWallet = (rfq.side == bs::network::Side::Sell) ? walletsMgr_->getHDRootForLeaf(ccWallet_->walletId()) : xbtWallet_; - if (!rootWallet) { - throw std::runtime_error("missing signing wallet"); - } - walletInfo_ = bs::hd::WalletInfo(walletsMgr_, rootWallet); - infoReqId_ = signingContainer_->GetInfo(walletInfo_.rootId().toStdString()); - - if (!dealerTx_.ParseFromString(BinaryData::CreateFromHex(quote_.dealerTransaction).toBinStr())) { - throw std::invalid_argument("invalid dealer's transaction"); - } -} - -ReqCCSettlementContainer::~ReqCCSettlementContainer() = default; - -bs::sync::PasswordDialogData ReqCCSettlementContainer::toPasswordDialogData(QDateTime timestamp) const -{ - bs::sync::PasswordDialogData dialogData = SettlementContainer::toPasswordDialogData(timestamp); - dialogData.setValue(PasswordDialogData::Market, "CC"); - dialogData.setValue(PasswordDialogData::AutoSignCategory, static_cast(bs::signer::AutoSignCategory::SettlementRequestor)); - dialogData.setValue(PasswordDialogData::LotSize, static_cast(lotSize_)); - - if (side() == bs::network::Side::Sell) { - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Delivery")); - } - else { - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Payment")); - } - - // rfq details - dialogData.setValue(PasswordDialogData::Price, UiUtils::displayPriceCC(price())); - dialogData.setValue(PasswordDialogData::Quantity, quantity()); - - // tx details - if (side() == bs::network::Side::Buy) { - dialogData.setValue(PasswordDialogData::TxInputProduct, UiUtils::XbtCurrency); - } - else { - dialogData.setValue(PasswordDialogData::TxInputProduct, product()); - } - - - // settlement details - dialogData.setValue(PasswordDialogData::DeliveryUTXOVerified, genAddrVerified_); - dialogData.setValue(PasswordDialogData::SigningAllowed, genAddrVerified_); - - dialogData.setValue(PasswordDialogData::RecipientsListVisible, true); - dialogData.setValue(PasswordDialogData::InputsListVisible, true); - - return dialogData; -} - -void ReqCCSettlementContainer::activate() -{ - if (side() == bs::network::Side::Buy) { - double balance = 0; - for (const auto &leaf : xbtWallet_->getGroup(xbtWallet_->getXBTGroupType())->getLeaves()) { - balance += assetMgr_->getBalance(bs::network::XbtCurrency, bs::UTXOReservationManager::kIncludeZcRequestor, leaf); - } - if (amount() > balance) { - emit paymentVerified(false, tr("Insufficient XBT balance in signing wallet")); - return; - } - } - - startTimer(kWaitTimeoutInSec); - - userKeyOk_ = false; - bool foundRecipAddr = false; - bool amountValid = false; - try { - signer_.deserializeState(dealerTx_); - foundRecipAddr = signer_.findRecipAddress(bs::Address::fromAddressString(rfq_.receiptAddress) - , [this, &amountValid](uint64_t value, uint64_t valReturn, uint64_t valInput) { - if ((quote_.side == bs::network::Side::Sell) && qFuzzyCompare(quantity(), value / lotSize_)) { - amountValid = valInput == (value + valReturn); - } - else if (quote_.side == bs::network::Side::Buy) { - const auto quoteVal = static_cast(amount() * BTCNumericTypes::BalanceDivider); - const auto diff = (quoteVal > value) ? quoteVal - value : value - quoteVal; - if (diff < 3) { - amountValid = valInput > (value + valReturn); - } - } - }); - } - catch (const std::exception &e) { - logger_->debug("Signer deser exc: {}", e.what()); - emit error(id(), bs::error::ErrorCode::InternalError - , tr("Failed to verify dealer's TX: %1").arg(QLatin1String(e.what()))); - } - - emit paymentVerified(foundRecipAddr && amountValid, QString{}); - - if (genAddress_.empty()) { - emit genAddressVerified(false, tr("GA is null")); - } - else if (side() == bs::network::Side::Buy) { - //Waiting for genesis address verification to complete... - - const auto &cbHasInput = [this, handle = validityFlag_.handle()](bool has) { - if (!handle.isValid()) { - return; - } - userKeyOk_ = has; - emit genAddressVerified(has, has ? QString{} : tr("GA check failed")); - }; - signer_.hasInputAddress(genAddress_, cbHasInput, lotSize_); - } - else { - userKeyOk_ = true; - emit genAddressVerified(true, QString{}); - } - - if (!createCCUnsignedTXdata()) { - userKeyOk_ = false; - emit error(id(), bs::error::ErrorCode::InternalError - , tr("Failed to create unsigned CC transaction")); - } -} - -void ReqCCSettlementContainer::deactivate() -{ - stopTimer(); -} - -// KLUDGE currently this code not just making unsigned TX, but also initiate signing -bool ReqCCSettlementContainer::createCCUnsignedTXdata() -{ - if (side() == bs::network::Side::Sell) { - const uint64_t spendVal = quantity() * assetMgr_->getCCLotSize(product()); - logger_->debug("[{}] sell amount={}, spend value = {}", __func__, quantity(), spendVal); - ccTxData_.walletIds = { ccWallet_->walletId() }; - ccTxData_.armorySigner_.deserializeState(dealerTx_); - const auto recipient = bs::Address::fromAddressString(dealerAddress_).getRecipient(bs::XBTAmount{ spendVal }); - if (recipient) { - ccTxData_.armorySigner_.addRecipient(recipient, RECIP_GROUP_SPEND_1); - } - else { - logger_->error("[{}] failed to create recipient from {} and value {}" - , __func__, dealerAddress_, spendVal); - return false; - } - - logger_->debug("[{}] {} CC inputs reserved ({} recipients)" - , __func__, ccTxData_.armorySigner_.getTxInCount(), - ccTxData_.armorySigner_.getTxOutCount()); - - // KLUDGE - in current implementation, we should sign first to have sell/buy process aligned - AcceptQuote(); - } - else { - const auto &cbFee = [this](float feePerByteArmory) { - auto feePerByte = std::max(feePerByteArmory, utxoReservationManager_->feeRatePb()); - const uint64_t spendVal = bs::XBTAmount(amount()).GetValue(); - auto inputsCb = [this, feePerByte, spendVal](const std::map &xbtInputs, bool useAllInputs = false) { - auto changeAddrCb = [this, feePerByte, xbtInputs, spendVal, useAllInputs](const bs::Address &changeAddr) { - try { - - const auto recipient = bs::Address::fromAddressString(dealerAddress_).getRecipient(bs::XBTAmount{ spendVal }); - if (!recipient) { - logger_->error("[{}] invalid recipient: {}", __func__, dealerAddress_); - return; - } - std::map>> recipientMap; - std::vector> recVec({recipient}); - recipientMap.emplace(RECIP_GROUP_SPEND_2, std::move(recVec)); - - ccTxData_ = bs::sync::WalletsManager::createPartialTXRequest(spendVal - , xbtInputs, changeAddr, feePerByte, armory_->topBlock() - , recipientMap, RECIP_GROUP_CHANG_2 - , dealerTx_, useAllInputs, UINT32_MAX, logger_); - - logger_->debug("{} inputs in ccTxData", ccTxData_.armorySigner_.getTxInCount()); - // Must release old reservation first (we reserve excessive XBT inputs in advance for CC buy requests)! - - auto resolveCB = [this]( - bs::error::ErrorCode result, const Codec_SignerState::SignerState &state) - { - utxoRes_.release(); - if (result != bs::error::ErrorCode::NoError) { - std::stringstream ss; - ss << "failed to resolve CC half reply with error code: " << (int)result; - throw std::runtime_error(ss.str()); - } - - ccTxData_.armorySigner_.deserializeState(state); - utxoRes_ = utxoReservationManager_->makeNewReservation(ccTxData_.getInputs(nullptr), id()); - AcceptQuote(); - }; - signingContainer_->resolvePublicSpenders(ccTxData_, resolveCB); - } - catch (const std::exception &e) { - SPDLOG_LOGGER_ERROR(logger_, "Failed to create partial CC TX " - "to {}: {}", dealerAddress_, e.what()); - emit error(id(), bs::error::ErrorCode::InternalError - , tr("Failed to create CC TX half")); - } - }; - xbtLeaves_.front()->getNewChangeAddress(changeAddrCb); - }; - if (manualXbtInputs_.empty()) { - std::vector utxos; - if (!xbtWallet_->canMixLeaves()) { - assert(walletPurpose_); - utxos = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet_->walletId(), *walletPurpose_, bs::UTXOReservationManager::kIncludeZcRequestor); - } - else { - utxos = utxoReservationManager_->getAvailableXbtUTXOs(xbtWallet_->walletId(), bs::UTXOReservationManager::kIncludeZcRequestor); - } - - auto fixedUtxo = utxoReservationManager_->convertUtxoToPartialFixedInput(xbtWallet_->walletId(), utxos); - inputsCb(fixedUtxo.inputs); - } else { - inputsCb(manualXbtInputs_, true); - } - }; - walletsMgr_->estimatedFeePerByte(0, cbFee, this); - } - - return true; -} - -void ReqCCSettlementContainer::AcceptQuote() -{ - if (side() == bs::network::Side::Sell) { - if (!ccTxData_.isValid()) { - logger_->error("[CCSettlementTransactionWidget::AcceptQuote] CC TX half wasn't created properly"); - emit error(id(), bs::error::ErrorCode::InternalError - , tr("Failed to create TX half")); - return; - } - } - signingContainer_->resolvePublicSpenders(ccTxData_, [this] - (bs::error::ErrorCode result, const Codec_SignerState::SignerState &state) - { - if (result == bs::error::ErrorCode::NoError) { - ccTxResolvedData_ = state; - emit sendOrder(); - } - else { - emit error(id(), result, bs::error::ErrorCodeToString(result)); - } - }); -} - -bool ReqCCSettlementContainer::startSigning(QDateTime timestamp) -{ - const auto &cbTx = [this, handle = validityFlag_.handle(), logger=logger_](bs::error::ErrorCode result, const BinaryData &signedTX) { - if (!handle.isValid()) { - logger->warn("[ReqCCSettlementContainer::onTXSigned] failed to sign TX half, already destroyed"); - return; - } - - ccSignId_ = 0; - - if (result == bs::error::ErrorCode::NoError) { - ccTxSigned_ = signedTX.toHexStr(); - - // notify RFQ dialog that signed half could be saved - emit txSigned(); - - // FIXME: disabled as it does not work correctly (signedTX txid is different from combined txid) -#if 0 - for (const auto &xbtLeaf : xbtLeaves_) { - xbtLeaf->setTransactionComment(signedTX, txComment()); - } - ccWallet_->setTransactionComment(signedTX, txComment()); -#endif - } - else if (result == bs::error::ErrorCode::TxCancelled) { - SettlementContainer::releaseUtxoRes(); - emit cancelTrade(clOrdId_); - } - else { - logger->error("[CCSettlementTransactionWidget::onTXSigned] CC TX sign failure: {}", bs::error::ErrorCodeToString(result).toStdString()); - emit error(id(), result, tr("Own TX half signing failed: %1") - .arg(bs::error::ErrorCodeToString(result))); - } - - // Call completed to remove from RfqStorage and cleanup memory - emit completed(id()); - }; - - ccSignId_ = signingContainer_->signSettlementPartialTXRequest(ccTxData_, toPasswordDialogData(timestamp), cbTx); - logger_->debug( - "[CCSettlementTransactionWidget::createCCSignedTXdata] {} recipients", - ccTxData_.armorySigner_.getTxInCount()); - return (ccSignId_ > 0); -} - -std::string ReqCCSettlementContainer::txComment() -{ - return std::string(bs::network::Side::toString(bs::network::Side::invert(quote_.side))) + " " - + quote_.security + " @ " + std::to_string(price()); -} - -void ReqCCSettlementContainer::onWalletInfo(unsigned int reqId, const bs::hd::WalletInfo &walletInfo) -{ - if (!infoReqId_ || (reqId != infoReqId_)) { - return; - } - - // just update walletInfo_ to save walletName and id - walletInfo_.setEncKeys(walletInfo.encKeys()); - walletInfo_.setEncTypes(walletInfo.encTypes()); - walletInfo_.setKeyRank(walletInfo.keyRank()); - - emit walletInfoReceived(); -} - -void ReqCCSettlementContainer::onGenAddressVerified(bool addressVerified, const QString &error) -{ - genAddrVerified_ = addressVerified; - - bs::sync::PasswordDialogData pd; - pd.setValue(PasswordDialogData::SettlementId, id()); - pd.setValue(PasswordDialogData::DeliveryUTXOVerified, addressVerified); - pd.setValue(PasswordDialogData::SigningAllowed, addressVerified); - signingContainer_->updateDialogData(pd); -} - -bool ReqCCSettlementContainer::cancel() -{ - deactivate(); - if (ccSignId_ != 0) { - signingContainer_->CancelSignTx(BinaryData::fromString(id())); - } - - SettlementContainer::releaseUtxoRes(); - emit settlementCancelled(); - - return true; -} - -std::string ReqCCSettlementContainer::txData() const -{ - if (!ccTxResolvedData_.IsInitialized()) { - logger_->error("[ReqCCSettlementContainer::txData] no resolved data"); - return {}; - } - return BinaryData::fromString(ccTxResolvedData_.SerializeAsString()).toHexStr(); -} - -void ReqCCSettlementContainer::setClOrdId(const std::string& clientOrderId) -{ - clOrdId_ = clientOrderId; -} diff --git a/BlockSettleUILib/Trading/ReqCCSettlementContainer.h b/BlockSettleUILib/Trading/ReqCCSettlementContainer.h deleted file mode 100644 index cb08c8c73..000000000 --- a/BlockSettleUILib/Trading/ReqCCSettlementContainer.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __REQ_CC_SETTLEMENT_CONTAINER_H__ -#define __REQ_CC_SETTLEMENT_CONTAINER_H__ - -#include -#include "CheckRecipSigner.h" -#include "SettlementContainer.h" -#include "CommonTypes.h" -#include "CoreWallet.h" -#include "QWalletInfo.h" -#include "UtxoReservationToken.h" - -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - class WalletsManager; - } - class UTXOReservationManager; -} -class ArmoryConnection; -class AssetManager; -class SignContainer; - - -class ReqCCSettlementContainer : public bs::SettlementContainer -{ - Q_OBJECT -public: - ReqCCSettlementContainer(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const bs::network::RFQ & - , const bs::network::Quote & - , const std::shared_ptr &xbtWallet - , const std::map &manualXbtInputs - , const std::shared_ptr &utxoReservationManager - , std::unique_ptr walletPurpose - , bs::UtxoReservationToken utxoRes - , bool expandTxDialogInfo); - ~ReqCCSettlementContainer() override; - - bool cancel() override; - - void activate() override; - void deactivate() override; - - std::string id() const override { return rfq_.requestId; } - bs::network::Asset::Type assetType() const override { return rfq_.assetType; } - std::string security() const override { return rfq_.security; } - std::string product() const override { return rfq_.product; } - bs::network::Side::Type side() const override { return rfq_.side; } - double quantity() const override { return quote_.quantity; } - double price() const override { return quote_.price; } - double amount() const override { return quantity() * price(); } - bs::sync::PasswordDialogData toPasswordDialogData(QDateTime timestamp) const override; - - bs::hd::WalletInfo walletInfo() const { return walletInfo_; } - std::string txData() const; - std::string txSignedData() const { return ccTxSigned_; } - - bool startSigning(QDateTime timestamp); - - void setClOrdId(const std::string& clientOrderId); - -signals: - void sendOrder(); - - void settlementCancelled(); - - void txSigned(); - void genAddressVerified(bool result, QString error); - void paymentVerified(bool result, QString error); - void walletInfoReceived(); - - void cancelTrade(const std::string& orderId); - -private slots: - void onWalletInfo(unsigned int reqId, const bs::hd::WalletInfo& walletInfo); - void onGenAddressVerified(bool addressVerified, const QString &error); - -private: - // read comments in source code - bool createCCUnsignedTXdata(); - std::string txComment(); - - void AcceptQuote(); - -private: - std::shared_ptr logger_; - std::shared_ptr signingContainer_; - std::shared_ptr xbtWallet_; - std::vector> xbtLeaves_; - std::shared_ptr ccWallet_; - std::shared_ptr assetMgr_; - std::shared_ptr walletsMgr_; - std::shared_ptr utxoReservationManager_; - std::shared_ptr armory_; - bs::network::RFQ rfq_; - bs::network::Quote quote_; - const bs::Address genAddress_; - const std::string dealerAddress_; - bs::CheckRecipSigner signer_; - - const uint64_t lotSize_; - unsigned int ccSignId_ = 0; - unsigned int infoReqId_ = 0; - bool userKeyOk_ = false; - - Codec_SignerState::SignerState dealerTx_; - bs::core::wallet::TXSignRequest ccTxData_; - Codec_SignerState::SignerState ccTxResolvedData_; - std::string ccTxSigned_; - bool genAddrVerified_ = false; - - bs::hd::WalletInfo walletInfo_; - std::map manualXbtInputs_; - - std::string clOrdId_; - -}; - -#endif // __REQ_CC_SETTLEMENT_CONTAINER_H__ diff --git a/BlockSettleUILib/Trading/ReqXBTSettlementContainer.cpp b/BlockSettleUILib/Trading/ReqXBTSettlementContainer.cpp deleted file mode 100644 index 3fdf6a8e1..000000000 --- a/BlockSettleUILib/Trading/ReqXBTSettlementContainer.cpp +++ /dev/null @@ -1,519 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "ReqXBTSettlementContainer.h" - -#include - -#include - -#include "AssetManager.h" -#include "AuthAddressManager.h" -#include "CheckRecipSigner.h" -#include "CurrencyPair.h" -#include "QuoteProvider.h" -#include "WalletSignerContainer.h" -#include "TradesUtils.h" -#include "UiUtils.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "UtxoReservationManager.h" - -#include - -using namespace bs::sync; - -Q_DECLARE_METATYPE(AddressVerificationState) - -ReqXBTSettlementContainer::ReqXBTSettlementContainer(const std::shared_ptr &logger - , const std::shared_ptr &authAddrMgr - , const std::shared_ptr &signContainer - , const std::shared_ptr &armory - , const std::shared_ptr &xbtWallet - , const std::shared_ptr &walletsMgr - , const bs::network::RFQ &rfq - , const bs::network::Quote "e - , const bs::Address &authAddr - , const std::map &utxosPayinFixed - , bs::UtxoReservationToken utxoRes - , const std::shared_ptr &utxoReservationManager - , std::unique_ptr walletPurpose - , const bs::Address &recvAddrIfSet - , bool expandTxDialogInfo - , uint64_t tier1XbtLimit) - : bs::SettlementContainer(std::move(utxoRes), std::move(walletPurpose), expandTxDialogInfo) - , logger_(logger) - , authAddrMgr_(authAddrMgr) - , walletsMgr_(walletsMgr) - , signContainer_(signContainer) - , armory_(armory) - , xbtWallet_(xbtWallet) - , rfq_(rfq) - , quote_(quote) - , recvAddrIfSet_(recvAddrIfSet) - , weSellXbt_(!rfq.isXbtBuy()) - , authAddr_(authAddr) - , utxosPayinFixed_(utxosPayinFixed) - , utxoReservationManager_(utxoReservationManager) -{ - assert(authAddr.isValid()); - - qRegisterMetaType(); - - connect(signContainer_.get(), &SignContainer::TXSigned, this, &ReqXBTSettlementContainer::onTXSigned); - - connect(this, &ReqXBTSettlementContainer::timerExpired, this, &ReqXBTSettlementContainer::onTimerExpired); - - CurrencyPair cp(quote_.security); - const bool isFxProd = (quote_.product != bs::network::XbtCurrency); - fxProd_ = cp.ContraCurrency(bs::network::XbtCurrency); - amount_ = isFxProd ? quantity() / price() : quantity(); - - const auto xbtAmount = bs::XBTAmount(amount_); - - // BST-2545: Use price as it see Genoa (and it computes it as ROUNDED_CCY / XBT) - const auto actualXbtPrice = UiUtils::actualXbtPrice(xbtAmount, price()); - - auto side = quote_.product == bs::network::XbtCurrency ? bs::network::Side::invert(quote_.side) : quote_.side; - comment_ = fmt::format("{} {} @ {}", bs::network::Side::toString(side) - , quote_.security, UiUtils::displayPriceXBT(actualXbtPrice).toStdString()); - - dealerAddressValidationRequired_ = xbtAmount > bs::XBTAmount(tier1XbtLimit); -} - -ReqXBTSettlementContainer::~ReqXBTSettlementContainer() = default; - -void ReqXBTSettlementContainer::acceptSpotXBT() -{ - emit acceptQuote(rfq_.requestId, "not used"); -} - -bool ReqXBTSettlementContainer::cancel() -{ - deactivate(); - - if (payinSignId_ != 0 || payoutSignId_ != 0) { - signContainer_->CancelSignTx(settlementId_); - } - - SettlementContainer::releaseUtxoRes(); - emit settlementCancelled(); - - return true; -} - -void ReqXBTSettlementContainer::onTimerExpired() -{ - cancel(); -} - -void ReqXBTSettlementContainer::activate() -{ - startTimer(kWaitTimeoutInSec); - - settlementIdHex_ = quote_.settlementId; - - addrVerificator_ = std::make_shared(logger_, armory_ - , [this, handle = validityFlag_.handle()](const bs::Address &address, AddressVerificationState state) - { - QMetaObject::invokeMethod(qApp, [this, handle, address, state] { - if (!handle.isValid()) { - return; - } - dealerVerifStateChanged(state); - }); - }); - - addrVerificator_->SetBSAddressList(authAddrMgr_->GetBSAddresses()); - - settlementId_ = BinaryData::CreateFromHex(quote_.settlementId); - userKey_ = BinaryData::CreateFromHex(quote_.requestorAuthPublicKey); - dealerAuthKey_ = BinaryData::CreateFromHex(quote_.dealerAuthPublicKey); - dealerAuthAddress_ = bs::Address::fromPubKey(dealerAuthKey_, AddressEntryType_P2WPKH); - - acceptSpotXBT(); - - const auto &authLeaf = walletsMgr_->getAuthWallet(); - signContainer_->setSettlAuthAddr(authLeaf->walletId(), settlementId_, authAddr_); -} - -void ReqXBTSettlementContainer::deactivate() -{ - stopTimer(); -} - -bs::sync::PasswordDialogData ReqXBTSettlementContainer::toPasswordDialogData(QDateTime timestamp) const -{ - bs::sync::PasswordDialogData dialogData = SettlementContainer::toPasswordDialogData(timestamp); - dialogData.setValue(PasswordDialogData::Market, "XBT"); - dialogData.setValue(PasswordDialogData::AutoSignCategory, static_cast(bs::signer::AutoSignCategory::SettlementRequestor)); - - // rfq details - QString qtyProd = UiUtils::XbtCurrency; - QString fxProd = QString::fromStdString(fxProd_); - - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Pay-In")); - dialogData.setValue(PasswordDialogData::Price, UiUtils::displayPriceXBT(price())); - dialogData.setValue(PasswordDialogData::FxProduct, fxProd); - - bool isFxProd = (quote_.product != bs::network::XbtCurrency); - - if (isFxProd) { - dialogData.setValue(PasswordDialogData::Quantity, tr("%1 %2") - .arg(UiUtils::displayAmountForProduct(quantity(), fxProd, bs::network::Asset::Type::SpotXBT)) - .arg(fxProd)); - - dialogData.setValue(PasswordDialogData::TotalValue, tr("%1 XBT") - .arg(UiUtils::displayAmount(quantity() / price()))); - } - else { - dialogData.setValue(PasswordDialogData::Quantity, tr("%1 XBT") - .arg(UiUtils::displayAmount(amount()))); - - dialogData.setValue(PasswordDialogData::TotalValue, tr("%1 %2") - .arg(UiUtils::displayAmountForProduct(amount() * price(), fxProd, bs::network::Asset::Type::SpotXBT)) - .arg(fxProd)); - } - - // settlement details - dialogData.setValue(PasswordDialogData::SettlementId, settlementIdHex_); - dialogData.setValue(PasswordDialogData::SettlementAddress, settlAddr_.display()); - - dialogData.setValue(PasswordDialogData::RequesterAuthAddress, authAddr_.display()); - dialogData.setValue(PasswordDialogData::RequesterAuthAddressVerified, true); - - dialogData.setValue(PasswordDialogData::ResponderAuthAddress, - dealerAuthAddress_.display()); - dialogData.setValue(PasswordDialogData::ResponderAuthAddressVerified, dealerVerifState_ == AddressVerificationState::Verified); - dialogData.setValue(PasswordDialogData::SigningAllowed, dealerVerifState_ == AddressVerificationState::Verified); - - // tx details - dialogData.setValue(PasswordDialogData::TxInputProduct, UiUtils::XbtCurrency); - - return dialogData; -} - -void ReqXBTSettlementContainer::dealerVerifStateChanged(AddressVerificationState state) -{ - dealerVerifState_ = state; - bs::sync::PasswordDialogData pd; - pd.setValue(PasswordDialogData::SettlementId, settlementIdHex_); - pd.setValue(PasswordDialogData::ResponderAuthAddress, dealerAuthAddress_.display()); - pd.setValue(PasswordDialogData::ResponderAuthAddressVerified, state == AddressVerificationState::Verified); - pd.setValue(PasswordDialogData::SigningAllowed, state == AddressVerificationState::Verified); - signContainer_->updateDialogData(pd); -} - -void ReqXBTSettlementContainer::cancelWithError(const QString& errorMessage, bs::error::ErrorCode code) -{ - emit cancelTrade(settlementIdHex_); - emit error(id(), code, errorMessage); - cancel(); - - // Call failed to remove from RfqStorage and cleanup memory - emit failed(id()); -} - -void ReqXBTSettlementContainer::onTXSigned(unsigned int idReq, BinaryData signedTX - , bs::error::ErrorCode errCode, std::string errTxt) -{ - if ((payoutSignId_ != 0) && (payoutSignId_ == idReq)) { - payoutSignId_ = 0; - - if (errCode == bs::error::ErrorCode::TxCancelled) { - SPDLOG_LOGGER_DEBUG(logger_, "cancel on a trade : {}", settlementIdHex_); - deactivate(); - emit cancelTrade(settlementIdHex_); - return; - } - - if ((errCode != bs::error::ErrorCode::NoError) || signedTX.empty()) { - logger_->warn("[ReqXBTSettlementContainer::onTXSigned] Pay-Out sign failure: {} ({})" - , (int)errCode, errTxt); - cancelWithError(tr("Pay-Out signing failed: %1").arg(bs::error::ErrorCodeToString(errCode)), errCode); - return; - } - - SPDLOG_LOGGER_DEBUG(logger_, "signed payout: {}", signedTX.toHexStr()); - - bs::tradeutils::PayoutVerifyArgs verifyArgs; - verifyArgs.signedTx = signedTX; - verifyArgs.settlAddr = settlAddr_; - verifyArgs.usedPayinHash = expectedPayinHash_; - verifyArgs.amount = bs::XBTAmount(amount_); - auto verifyResult = bs::tradeutils::verifySignedPayout(verifyArgs); - if (!verifyResult.success) { - SPDLOG_LOGGER_ERROR(logger_, "payout verification failed: {}", verifyResult.errorMsg); - cancelWithError(tr("payin verification failed: %1").arg(bs::error::ErrorCodeToString(errCode)), errCode); - return; - } - - emit sendSignedPayoutToPB(settlementIdHex_, signedTX); - - for (const auto &leaf : xbtWallet_->getGroup(xbtWallet_->getXBTGroupType())->getLeaves()) { - leaf->setTransactionComment(signedTX, comment_); - } -// walletsMgr_->getSettlementWallet()->setTransactionComment(payoutData_, comment_); //TODO: later - - // OK. if payout created - settletlement accepted for this RFQ - deactivate(); - emit settlementAccepted(); - - // Call completed to remove from RfqStorage and cleanup memory - emit completed(id()); - } - - if ((payinSignId_ != 0) && (payinSignId_ == idReq)) { - payinSignId_ = 0; - - if (errCode == bs::error::ErrorCode::TxCancelled) { - SPDLOG_LOGGER_DEBUG(logger_, "cancel on a trade : {}", settlementIdHex_); - deactivate(); - emit cancelTrade(settlementIdHex_); - return; - } - - if ((errCode != bs::error::ErrorCode::NoError) || signedTX.empty()) { - SPDLOG_LOGGER_ERROR(logger_, "failed to create pay-in TX: {} ({})", static_cast(errCode), errTxt); - cancelWithError(tr("Failed to create Pay-In TX: %1").arg(bs::error::ErrorCodeToString(errCode)), errCode); - return; - } - - try { - const Tx tx(signedTX); - if (!tx.isInitialized()) { - throw std::runtime_error("uninited TX"); - } - - if (tx.getThisHash() != expectedPayinHash_) { - emit cancelWithError(tr("payin hash mismatch"), bs::error::ErrorCode::TxInvalidRequest); - return; - } - } - catch (const std::exception &e) { - emit cancelWithError(tr("invalid signed pay-in"), bs::error::ErrorCode::TxInvalidRequest); - return; - } - - for (const auto &leaf : xbtWallet_->getGroup(xbtWallet_->getXBTGroupType())->getLeaves()) { - leaf->setTransactionComment(signedTX, comment_); - } - - emit sendSignedPayinToPB(settlementIdHex_, signedTX); - - // OK. if payin created - settletlement accepted for this RFQ - deactivate(); - emit settlementAccepted(); - - // Call completed to remove from RfqStorage and cleanup memory - emit completed(id()); - } -} - -void ReqXBTSettlementContainer::onUnsignedPayinRequested(const std::string& settlementId) -{ - if (settlementIdHex_ != settlementId) { - SPDLOG_LOGGER_ERROR(logger_, "invalid id : {} . {} expected", settlementId, settlementIdHex_); - return; - } - - if (!weSellXbt_) { - SPDLOG_LOGGER_ERROR(logger_, "customer buy on thq rfq {}. should not create unsigned payin" - , settlementId); - return; - } - - SPDLOG_LOGGER_DEBUG(logger_, "unsigned payin requested: {}", settlementId); - - bs::tradeutils::PayinArgs args; - initTradesArgs(args, settlementId); - args.fixedInputs.reserve(utxosPayinFixed_.size()); - for (const auto &input : utxosPayinFixed_) { - args.fixedInputs.push_back(input.first); - } - - const auto xbtGroup = xbtWallet_->getGroup(xbtWallet_->getXBTGroupType()); - if (!xbtWallet_->canMixLeaves()) { - assert(walletPurpose_); - const auto leaf = xbtGroup->getLeaf(*walletPurpose_); - args.inputXbtWallets.push_back(leaf); - } - else { - for (const auto &leaf : xbtGroup->getLeaves()) { - args.inputXbtWallets.push_back(leaf); - } - } - - args.utxoReservation = bs::UtxoReservation::instance(); - - auto payinCb = bs::tradeutils::PayinResultCb([this, handle = validityFlag_.handle()] - (bs::tradeutils::PayinResult result) - { - QMetaObject::invokeMethod(qApp, [this, handle, result = std::move(result)] { - if (!handle.isValid()) { - return; - } - - if (!result.success) { - SPDLOG_LOGGER_ERROR(logger_, "payin sign request creation failed: {}", result.errorMsg); - cancelWithError(tr("payin failed"), bs::error::ErrorCode::InternalError); - return; - } - - settlAddr_ = result.settlementAddr; - - const auto list = authAddrMgr_->GetSubmittedAddressList(); - const auto userAddress = bs::Address::fromPubKey(userKey_, AddressEntryType_P2WPKH); - userKeyOk_ = (std::find(list.begin(), list.end(), userAddress) != list.end()); - if (!userKeyOk_) { - SPDLOG_LOGGER_WARN(logger_, "userAddr {} not found in verified addrs list ({})" - , userAddress.display(), list.size()); - return; - } - - if (dealerAddressValidationRequired_) { - addrVerificator_->addAddress(dealerAuthAddress_); - addrVerificator_->startAddressVerification(); - } else { - dealerVerifState_ = AddressVerificationState::Verified; - } - - unsignedPayinRequest_ = std::move(result.signRequest); - - // Make new reservation only for automatic inputs. - // Manual inputs should be already reserved. - if (utxosPayinFixed_.empty()) { - utxoRes_ = utxoReservationManager_->makeNewReservation( - unsignedPayinRequest_.getInputs(nullptr), id()); - } - - emit sendUnsignedPayinToPB(settlementIdHex_ - , bs::network::UnsignedPayinData{ unsignedPayinRequest_.serializeState().SerializeAsString() }); - - const auto &authLeaf = walletsMgr_->getAuthWallet(); - signContainer_->setSettlCP(authLeaf->walletId(), result.payinHash, settlementId_, dealerAuthKey_); - }); - }); - - bs::tradeutils::createPayin(std::move(args), std::move(payinCb)); -} - -void ReqXBTSettlementContainer::onSignedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash, QDateTime timestamp) -{ - if (settlementIdHex_ != settlementId) { - SPDLOG_LOGGER_ERROR(logger_, "invalid id : {} . {} expected", settlementId, settlementIdHex_); - return; - } - - startTimer(kWaitTimeoutInSec); - - SPDLOG_LOGGER_DEBUG(logger_, "create payout for {} on {} for {}", settlementId, payinHash.toHexStr(), amount_); - expectedPayinHash_ = payinHash; - - bs::tradeutils::PayoutArgs args; - initTradesArgs(args, settlementId); - args.payinTxId = payinHash; - args.recvAddr = recvAddrIfSet_; - - const auto xbtGroup = xbtWallet_->getGroup(xbtWallet_->getXBTGroupType()); - if (!xbtWallet_->canMixLeaves()) { - assert(walletPurpose_); - const auto leaf = xbtGroup->getLeaf(*walletPurpose_); - args.outputXbtWallet = leaf; - } - else { - args.outputXbtWallet = xbtGroup->getLeaves().at(0); - } - - auto payoutCb = bs::tradeutils::PayoutResultCb([this, payinHash, timestamp, handle = validityFlag_.handle()] - (bs::tradeutils::PayoutResult result) - { - QMetaObject::invokeMethod(qApp, [this, payinHash, handle, timestamp, result = std::move(result)] { - if (!handle.isValid()) { - return; - } - - if (!result.success) { - SPDLOG_LOGGER_ERROR(logger_, "creating payout failed: {}", result.errorMsg); - cancelWithError(tr("payout failed"), bs::error::ErrorCode::InternalError); - return; - } - - settlAddr_ = result.settlementAddr; - - bs::sync::PasswordDialogData dlgData = toPayOutTxDetailsPasswordDialogData(result.signRequest, timestamp); - dlgData.setValue(PasswordDialogData::Market, "XBT"); - dlgData.setValue(PasswordDialogData::SettlementId, settlementIdHex_); - dlgData.setValue(PasswordDialogData::ResponderAuthAddressVerified, true); - dlgData.setValue(PasswordDialogData::SigningAllowed, true); - - SPDLOG_LOGGER_DEBUG(logger_, "pay-out fee={}, qty={} ({}), payin hash={}" - , result.signRequest.fee, amount_, amount_ * BTCNumericTypes::BalanceDivider, payinHash.toHexStr(true)); - - //note: signRequest should prolly be a shared_ptr - auto signerObj = result.signRequest; - payoutSignId_ = signContainer_->signSettlementPayoutTXRequest(signerObj - , {settlementId_, dealerAuthKey_, true}, dlgData); - }); - }); - bs::tradeutils::createPayout(std::move(args), std::move(payoutCb)); - -} - -void ReqXBTSettlementContainer::onSignedPayinRequested(const std::string& settlementId - , const BinaryData &payinHash, QDateTime timestamp) -{ - if (settlementIdHex_ != settlementId) { - SPDLOG_LOGGER_ERROR(logger_, "invalid id: {} - {} expected", settlementId - , settlementIdHex_); - return; - } - - if (payinHash.empty()) { - logger_->error("[ReqXBTSettlementContainer::onSignedPayinRequested] missing expected payin hash"); - emit cancelWithError(tr("payin hash mismatch"), bs::error::ErrorCode::TxInvalidRequest); - return; - } - - expectedPayinHash_ = payinHash; - - startTimer(kWaitTimeoutInSec); - - if (!weSellXbt_) { - SPDLOG_LOGGER_ERROR(logger_, "customer buy on thq rfq {}. should not sign payin", settlementId); - return; - } - - if (!unsignedPayinRequest_.isValid()) { - SPDLOG_LOGGER_ERROR(logger_, "unsigned payin request is invalid: {}", settlementIdHex_); - return; - } - - SPDLOG_LOGGER_DEBUG(logger_, "signed payin requested {}", settlementId); - - // XXX check unsigned payin? - - bs::sync::PasswordDialogData dlgData = toPasswordDialogData(timestamp); - dlgData.setValue(PasswordDialogData::SettlementPayInVisible, true); - - payinSignId_ = signContainer_->signSettlementTXRequest(unsignedPayinRequest_, dlgData); -} - -void ReqXBTSettlementContainer::initTradesArgs(bs::tradeutils::Args &args, const std::string &settlementId) -{ - args.amount = bs::XBTAmount{amount_}; - args.settlementId = BinaryData::CreateFromHex(settlementId); - args.ourAuthAddress = authAddr_; - args.cpAuthPubKey = dealerAuthKey_; - args.walletsMgr = walletsMgr_; - args.armory = armory_; - args.signContainer = signContainer_; - args.feeRatePb_ = utxoReservationManager_->feeRatePb(); -} diff --git a/BlockSettleUILib/Trading/ReqXBTSettlementContainer.h b/BlockSettleUILib/Trading/ReqXBTSettlementContainer.h deleted file mode 100644 index 8055f1173..000000000 --- a/BlockSettleUILib/Trading/ReqXBTSettlementContainer.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __REQ_XBT_SETTLEMENT_CONTAINER_H__ -#define __REQ_XBT_SETTLEMENT_CONTAINER_H__ - -#include -#include -#include "AddressVerificator.h" -#include "BSErrorCode.h" -#include "QWalletInfo.h" -#include "SettlementContainer.h" - -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - class WalletsManager; - } - namespace tradeutils { - struct Args; - } - class UTXOReservationManager; -} -class AddressVerificator; -class ArmoryConnection; -class AuthAddressManager; -class WalletSignerContainer; -class QuoteProvider; - - -class ReqXBTSettlementContainer : public bs::SettlementContainer -{ - Q_OBJECT -public: - ReqXBTSettlementContainer(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &xbtWallet - , const std::shared_ptr & - , const bs::network::RFQ & - , const bs::network::Quote & - , const bs::Address &authAddr - , const std::map &utxosPayinFixed - , bs::UtxoReservationToken utxoRes - , const std::shared_ptr &utxoReservationManager - , std::unique_ptr walletPurpose - , const bs::Address &recvAddrIfSet - , bool expandTxDialogInfo - , uint64_t tier1XbtLimit); - ~ReqXBTSettlementContainer() override; - - bool cancel() override; - - void activate() override; - void deactivate() override; - - std::string id() const override { return quote_.requestId; } - bs::network::Asset::Type assetType() const override { return rfq_.assetType; } - std::string security() const override { return rfq_.security; } - std::string product() const override { return rfq_.product; } - bs::network::Side::Type side() const override { return rfq_.side; } - double quantity() const override { return quote_.quantity; } - double price() const override { return quote_.price; } - double amount() const override { return amount_; } - bs::sync::PasswordDialogData toPasswordDialogData(QDateTime timestamp) const override; - - void onUnsignedPayinRequested(const std::string& settlementId); - void onSignedPayoutRequested(const std::string& settlementId, const BinaryData& payinHash - , QDateTime timestamp); - void onSignedPayinRequested(const std::string& settlementId, const BinaryData &payinHash - , QDateTime timestamp); - -signals: - void settlementCancelled(); - void settlementAccepted(); - void acceptQuote(std::string reqId, std::string hexPayoutTx); - - void sendUnsignedPayinToPB(const std::string& settlementId, const bs::network::UnsignedPayinData& unsignedPayinData); - void sendSignedPayinToPB(const std::string& settlementId, const BinaryData& signedPayin); - void sendSignedPayoutToPB(const std::string& settlementId, const BinaryData& signedPayout); - - void cancelTrade(const std::string& settlementId); - -private slots: - void onTXSigned(unsigned int id, BinaryData signedTX, bs::error::ErrorCode, std::string error); - void onTimerExpired(); - -private: - void acceptSpotXBT(); - void dealerVerifStateChanged(AddressVerificationState); - - void cancelWithError(const QString& errorMessage, bs::error::ErrorCode code); - - void initTradesArgs(bs::tradeutils::Args &args, const std::string &settlementId); - -private: - std::shared_ptr logger_; - std::shared_ptr authAddrMgr_; - std::shared_ptr walletsMgr_; - std::shared_ptr signContainer_; - std::shared_ptr armory_; - std::shared_ptr xbtWallet_; - std::shared_ptr utxoReservationManager_; - - bs::network::RFQ rfq_; - bs::network::Quote quote_; - bs::Address settlAddr_; - - std::shared_ptr addrVerificator_; - - double amount_{}; - std::string fxProd_; - BinaryData settlementId_; - std::string settlementIdHex_; - BinaryData userKey_; - BinaryData dealerAuthKey_; - bs::Address recvAddrIfSet_; - AddressVerificationState dealerVerifState_ = AddressVerificationState::VerificationFailed; - - std::string comment_; - const bool weSellXbt_; - bool userKeyOk_ = false; - - unsigned int payinSignId_ = 0; - unsigned int payoutSignId_ = 0; - - const bs::Address authAddr_; - bs::Address dealerAuthAddress_; - - bs::core::wallet::TXSignRequest unsignedPayinRequest_; - BinaryData expectedPayinHash_; - std::map utxosPayinFixed_; - - bool tradeCancelled_ = false; - bool dealerAddressValidationRequired_ = true; -}; - -#endif // __REQ_XBT_SETTLEMENT_CONTAINER_H__ diff --git a/BlockSettleUILib/Trading/RequestingQuoteWidget.cpp b/BlockSettleUILib/Trading/RequestingQuoteWidget.cpp deleted file mode 100644 index e399546e0..000000000 --- a/BlockSettleUILib/Trading/RequestingQuoteWidget.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RequestingQuoteWidget.h" -#include "ui_RequestingQuoteWidget.h" -#include - -#include "AssetManager.h" -#include "BlockDataManagerConfig.h" -#include "CelerClient.h" -#include "CurrencyPair.h" -#include "UiUtils.h" -#include "UtxoReservationManager.h" - -// XXX [AT] : possible concurent change of states - could lead to multiple signals emited -// add atomic flag - -static const char* WaitingPropertyName = "statusWarning"; -static const char* RepliedPropertyName = "statusSuccess"; -static const char* RejectedPropertyName = "statusImportant"; - -RequestingQuoteWidget::RequestingQuoteWidget(QWidget* parent) - : QWidget(parent) - , ui_(new Ui::RequestingQuoteWidget()) - , requestTimer_(this) -{ - ui_->setupUi(this); - - ui_->labelQuoteValue->setText(tr("Waiting for quote...")); - ui_->labelDetails->clear(); - ui_->labelDetails->hide(); - ui_->labelQuoteValue->show(); - - ui_->pushButtonAccept->hide(); - ui_->labelHint->clear(); - ui_->labelHint->hide(); - - setupTimer(Indicative, QDateTime::currentDateTime().addSecs(30)); - - connect(ui_->pushButtonCancel, &QPushButton::clicked, this, &RequestingQuoteWidget::onCancel); - connect(ui_->pushButtonAccept, &QPushButton::clicked, this, &RequestingQuoteWidget::onAccept); -} - -RequestingQuoteWidget::~RequestingQuoteWidget() = default; - -void RequestingQuoteWidget::SetCelerClient(std::shared_ptr celerClient) { - celerClient_ = celerClient; - - connect(celerClient_.get(), &BaseCelerClient::OnConnectionClosed, - this, &RequestingQuoteWidget::onCelerDisconnected); -} - -void RequestingQuoteWidget::setupTimer(RequestingQuoteWidget::Status status, const QDateTime &expTime) -{ - ui_->pushButtonAccept->setEnabled(status == Tradeable); - - timeoutReply_ = expTime; - - ui_->progressBar->setMaximum(QDateTime::currentDateTime().msecsTo(timeoutReply_)); - ui_->progressBar->setValue(ui_->progressBar->maximum()); - ticker(); - - requestTimer_.setInterval(500); - connect(&requestTimer_, &QTimer::timeout, this, &RequestingQuoteWidget::ticker); - requestTimer_.start(); -} - -void RequestingQuoteWidget::onCancel() -{ - requestTimer_.stop(); - if (quote_.quotingType == bs::network::Quote::Tradeable) { - emit requestTimedOut(); - } else { - emit cancelRFQ(); - } -} - -void RequestingQuoteWidget::ticker() -{ - auto timeDiff = QDateTime::currentDateTime().msecsTo(timeoutReply_); - - if (timeDiff < -5000) { - requestTimer_.stop(); - emit requestTimedOut(); - } - else { - ui_->progressBar->setValue(timeDiff); - ui_->progressBar->setFormat(tr("%1 second(s) remaining") - .arg(QString::number(timeDiff > 0 ? timeDiff/1000 : 0))); - - ui_->labelTimeLeft->setText(tr("%1 second(s) remaining") - .arg(QString::number(timeDiff > 0 ? timeDiff/1000 : 0))); - } -} - -void RequestingQuoteWidget::onOrderFilled(const std::string "eId) -{ - if (quote_.quoteId == quoteId) { - emit quoteFinished(); - } -} - -void RequestingQuoteWidget::onOrderFailed(const std::string& quoteId, const std::string &reason) -{ - Q_UNUSED(reason); - if (quote_.quoteId == quoteId) { - emit quoteFailed(); - } -} - -bool RequestingQuoteWidget::onQuoteReceived(const bs::network::Quote& quote) -{ - if (quote.requestId != rfq_.requestId) { - return false; - } - - quote_ = quote; - if (quote_.product.empty()) { - quote_.product = rfq_.product; - } - - if (quote_.quotingType == bs::network::Quote::Tradeable) { - if (!balanceOk_) { - return false; - } - - if (quote.assetType == bs::network::Asset::SpotFX) { - ui_->pushButtonAccept->show(); - setupTimer(Tradeable, quote.expirationTime.addMSecs(quote.timeSkewMs)); - } else { - onAccept(); - } - - return true; - } - - if (rfq_.side == bs::network::Side::Buy && rfq_.assetType != bs::network::Asset::SpotFX) { - double amount = 0; - if (rfq_.assetType == bs::network::Asset::PrivateMarket) { - amount = rfq_.quantity * quote_.price; - } - else if (rfq_.product != bs::network::XbtCurrency) { - amount = rfq_.quantity / quote_.price; - } - } - - timeoutReply_ = quote.expirationTime.addMSecs(quote.timeSkewMs); - - const auto assetType = assetManager_->GetAssetTypeForSecurity(quote.security); - ui_->labelQuoteValue->setText(UiUtils::displayPriceForAssetType(quote.price, assetType)); - ui_->labelQuoteValue->show(); - - if (quote.assetType == bs::network::Asset::SpotFX) { - ui_->labelHint->clear(); - ui_->labelHint->hide(); - } - - double value = rfq_.quantity * quote.price; - - QString productString = QString::fromStdString(rfq_.product); - QString productAmountString = UiUtils::displayAmountForProduct(rfq_.quantity, productString, rfq_.assetType); - - QString contrProductString; - QString valueString; - - CurrencyPair cp(rfq_.security); - - if (cp.NumCurrency() != rfq_.product) { - value = rfq_.quantity / quote.price; - contrProductString = QString::fromStdString(cp.NumCurrency()); - } else { - contrProductString = QString::fromStdString(cp.DenomCurrency()); - } - - valueString = UiUtils::displayAmountForProduct(value, contrProductString, rfq_.assetType); - - if (rfq_.side == bs::network::Side::Buy) { - const auto currency = contrProductString.toStdString(); - const auto balance = assetManager_->getBalance(currency, bs::UTXOReservationManager::kIncludeZcRequestor, nullptr); - balanceOk_ = (value < balance); - ui_->pushButtonAccept->setEnabled(balanceOk_); - if (!balanceOk_) { - ui_->labelHint->setText(tr("Insufficient balance")); - ui_->labelHint->show(); - } - } - - if (rfq_.side == bs::network::Side::Buy && !balanceOk_) { - return true; - } - - ui_->labelDetails->setText(tr("%1 %2 %3\n%4 %5 %6") - .arg((rfq_.side == bs::network::Side::Buy) ? tr("Receive") : tr("Deliver")) - .arg(productAmountString) - .arg(productString) - .arg((rfq_.side == bs::network::Side::Buy) ? tr("Deliver") : tr("Receive")) - .arg(valueString) - .arg(contrProductString)); - ui_->labelDetails->show(); - - return true; -} - -void RequestingQuoteWidget::onQuoteCancelled(const QString &reqId, bool byUser) -{ - if (!byUser && (reqId.toStdString() == rfq_.requestId)) { - quote_ = bs::network::Quote(); - ui_->labelQuoteValue->setText(tr("Waiting for quote...")); - ui_->labelDetails->clear(); - ui_->labelDetails->hide(); - } -} - -void RequestingQuoteWidget::onReject(const QString &reqId, const QString &reason) -{ - if (reqId.toStdString() == rfq_.requestId) { - ui_->pushButtonAccept->setEnabled(false); - ui_->labelQuoteValue->setText(tr("Rejected: %1").arg(reason)); - ui_->labelQuoteValue->show(); - } -} - -void RequestingQuoteWidget::onCelerDisconnected() -{ - onCancel(); -} - -void RequestingQuoteWidget::populateDetails(const bs::network::RFQ& rfq) -{ - rfq_ = rfq; - - ui_->labelProductGroup->setText(tr(bs::network::Asset::toString(rfq.assetType))); - ui_->labelSecurityId->setText(QString::fromStdString(rfq.security)); - ui_->labelProduct->setText(QString::fromStdString(rfq.product)); - ui_->labelSide->setText(tr(bs::network::Side::toString(rfq.side))); - - switch (rfq.assetType) { - case bs::network::Asset::SpotFX: - ui_->labelQuantity->setText(UiUtils::displayCurrencyAmount(rfq.quantity)); - break; - case bs::network::Asset::SpotXBT: - ui_->labelQuantity->setText(UiUtils::displayQty(rfq.quantity, rfq.product)); - break; - case bs::network::Asset::PrivateMarket: - ui_->labelQuantity->setText(UiUtils::displayCCAmount(rfq.quantity)); - break; - default: break; - } -} - -void RequestingQuoteWidget::onAccept() -{ - requestTimer_.stop(); - ui_->progressBar->hide(); - ui_->pushButtonAccept->setEnabled(false); - if (bs::network::Asset::SpotXBT == rfq_.assetType) { - ui_->labelHint->setText(tr("Awaiting Settlement Pay-Out Execution")); - ui_->labelHint->show(); - } - ui_->labelTimeLeft->clear(); - - emit quoteAccepted(QString::fromStdString(rfq_.requestId), quote_); -} - -void RequestingQuoteWidget::SetQuoteDetailsState(QuoteDetailsState state) -{ - ui_->widgetQuoteDetails->setProperty(WaitingPropertyName, false); - ui_->widgetQuoteDetails->setProperty(RepliedPropertyName, false); - ui_->widgetQuoteDetails->setProperty(RejectedPropertyName, false); - - switch (state) - { - case Waiting: - ui_->widgetQuoteDetails->setProperty(WaitingPropertyName, true); - break; - case Replied: - ui_->widgetQuoteDetails->setProperty(RepliedPropertyName, true); - break; - case Rejected: - ui_->widgetQuoteDetails->setProperty(RejectedPropertyName, true); - break; - } - - ui_->widgetQuoteDetails->style()->unpolish(ui_->widgetQuoteDetails); - ui_->widgetQuoteDetails->style()->polish(ui_->widgetQuoteDetails); -} diff --git a/BlockSettleUILib/Trading/RequestingQuoteWidget.h b/BlockSettleUILib/Trading/RequestingQuoteWidget.h deleted file mode 100644 index 4d883386a..000000000 --- a/BlockSettleUILib/Trading/RequestingQuoteWidget.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __REQUESTING_QUOTE_WIDGET_H__ -#define __REQUESTING_QUOTE_WIDGET_H__ - -#include -#include -#include -#include -#include "CommonTypes.h" - - -namespace Ui { - class RequestingQuoteWidget; -} -class AssetManager; -class BaseCelerClient; - -class RequestingQuoteWidget : public QWidget -{ -Q_OBJECT - -public: - RequestingQuoteWidget(QWidget* parent = nullptr ); - ~RequestingQuoteWidget() override; - - void SetAssetManager(const std::shared_ptr &assetManager) { - assetManager_ = assetManager; - } - - void SetCelerClient(std::shared_ptr celerClient); - - void populateDetails(const bs::network::RFQ& rfq); - -public slots: - void ticker(); - bool onQuoteReceived(const bs::network::Quote& quote); - void onQuoteCancelled(const QString &reqId, bool byUser); - void onOrderFilled(const std::string "eId); - void onOrderFailed(const std::string& quoteId, const std::string& reason); - void onReject(const QString &reqId, const QString &reason); - void onCelerDisconnected(); - -signals: - void cancelRFQ(); - void requestTimedOut(); - void quoteAccepted(const QString &reqId, const bs::network::Quote& quote); - void quoteFinished(); - void quoteFailed(); - -private: - enum Status { - Indicative, - Tradeable - }; - - enum QuoteDetailsState - { - Waiting, - Rejected, - Replied - }; - - void SetQuoteDetailsState(QuoteDetailsState state); - -private: - std::unique_ptr ui_; - QTimer requestTimer_; - QDateTime timeoutReply_; - bs::network::RFQ rfq_; - bs::network::Quote quote_; - std::shared_ptr assetManager_; - bool balanceOk_ = true; - std::shared_ptr celerClient_; - -private: - void setupTimer(Status status, const QDateTime &expTime); - void onCancel(); - void onAccept(); -}; - -#endif // __REQUESTING_QUOTE_WIDGET_H__ diff --git a/BlockSettleUILib/Trading/RequestingQuoteWidget.ui b/BlockSettleUILib/Trading/RequestingQuoteWidget.ui deleted file mode 100644 index ae224c9ff..000000000 --- a/BlockSettleUILib/Trading/RequestingQuoteWidget.ui +++ /dev/null @@ -1,691 +0,0 @@ - - - - RequestingQuoteWidget - - - - 0 - 0 - 340 - 420 - - - - - 0 - 0 - - - - - 340 - 420 - - - - Form - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 100 - - - - - - - - 0 - 0 - - - - REQUEST OVERVIEW - - - true - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Product Group - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - TextLabel - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Security ID - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - TextLabel - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Product - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - TextLabel - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Side - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - TextLabel - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Quantity - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - TextLabel - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - - - - - - 0 - 0 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 0 - 46 - - - - true - - - - 0 - - - 20 - - - 5 - - - 20 - - - 5 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 75 - true - - - - - - - Awaiting Quote... - - - Qt::AlignCenter - - - 0 - - - true - - - - - - - - 0 - 0 - - - - Deliver 1.0 EUR -Receive 10.28 SEK - - - Qt::AlignCenter - - - true - - - false - - - - - - - As the request is priced, the most competitive quote will be displayed. - - - Qt::AlignCenter - - - true - - - true - - - true - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 7 - - - - - 16777215 - 7 - - - - 24 - - - Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing - - - false - - - false - - - - - - - - - - Qt::AlignCenter - - - - - - - - 0 - 1 - - - - - 16777215 - 1 - - - - Qt::Horizontal - - - - - - - - - - - - - - 0 - 0 - - - - true - - - - 10 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - - 80 - 0 - - - - - - - Cancel - - - - - - - false - - - - 0 - 0 - - - - - 80 - 0 - - - - false - - - - - - Accept - - - true - - - - - - - - - - - diff --git a/BlockSettleUILib/Trading/RfqStorage.cpp b/BlockSettleUILib/Trading/RfqStorage.cpp deleted file mode 100644 index 00a65a867..000000000 --- a/BlockSettleUILib/Trading/RfqStorage.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "RfqStorage.h" - -#include "SettlementContainer.h" - -RfqStorage::RfqStorage() = default; - -RfqStorage::~RfqStorage() = default; - -void RfqStorage::addSettlementContainer(std::shared_ptr rfq) -{ - const auto id = rfq->id(); - - auto deleteCb = [this, handle = validityFlag_.handle(), id] { - if (!handle.isValid()) { - return; - } - auto it = rfqs_.find(id); - if (it == rfqs_.end()) { - return; - } - it->second->deactivate(); - rfqs_.erase(it); - }; - - // Use QueuedConnection so SettlementContainer is destroyed later - QObject::connect(rfq.get(), &bs::SettlementContainer::completed, this, deleteCb, Qt::QueuedConnection); - QObject::connect(rfq.get(), &bs::SettlementContainer::failed, this, deleteCb, Qt::QueuedConnection); - QObject::connect(rfq.get(), &bs::SettlementContainer::timerExpired, this, deleteCb, Qt::QueuedConnection); - - rfqs_[rfq->id()] = std::move(rfq); -} - -bs::SettlementContainer *RfqStorage::settlementContainer(const std::string &id) const -{ - auto it = rfqs_.find(id); - if (it == rfqs_.end()) { - return nullptr; - } - return it->second.get(); -} diff --git a/BlockSettleUILib/Trading/RfqStorage.h b/BlockSettleUILib/Trading/RfqStorage.h deleted file mode 100644 index 01bc73d11..000000000 --- a/BlockSettleUILib/Trading/RfqStorage.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef RFQ_STORAGE_H -#define RFQ_STORAGE_H - -#include -#include -#include - -#include "ValidityFlag.h" - -namespace bs { - class SettlementContainer; -} - -// Use to store and release memory for bs::SettlementContainer -class RfqStorage : public QObject -{ - Q_OBJECT - -public: - RfqStorage(); - ~RfqStorage(); - - void addSettlementContainer(std::shared_ptr rfq); - - bs::SettlementContainer *settlementContainer(const std::string &id) const; - -private: - std::unordered_map> rfqs_; - - ValidityFlag validityFlag_; - -}; - -#endif diff --git a/BlockSettleUILib/Trading/SettlementContainer.cpp b/BlockSettleUILib/Trading/SettlementContainer.cpp deleted file mode 100644 index 448c4e452..000000000 --- a/BlockSettleUILib/Trading/SettlementContainer.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "SettlementContainer.h" -#include "UiUtils.h" - -using namespace bs; -using namespace bs::sync; - -namespace { - - const auto kUtxoReleaseDelay = std::chrono::seconds(15); - -} // namespace - -SettlementContainer::SettlementContainer(UtxoReservationToken utxoRes - , std::unique_ptr walletPurpose, bool expandTxDialogInfo) - : QObject(nullptr) - , utxoRes_(std::move(utxoRes)) - , walletPurpose_(std::move(walletPurpose)) - , expandTxDialogInfo_(expandTxDialogInfo) -{} - -SettlementContainer::~SettlementContainer() -{ - if (utxoRes_.isValid()) { - QTimer::singleShot(kUtxoReleaseDelay, [utxoRes = std::move(utxoRes_)] () mutable { - utxoRes.release(); - }); - } -} - -sync::PasswordDialogData SettlementContainer::toPasswordDialogData(QDateTime timestamp) const -{ - bs::sync::PasswordDialogData info; - - info.setValue(PasswordDialogData::SettlementId, id()); - info.setValue(PasswordDialogData::DurationLeft, durationMs()); - info.setValue(PasswordDialogData::DurationTotal, (int)kWaitTimeoutInSec * 1000); - - // Set timestamp that will be used by auth eid server to update timers. - info.setValue(PasswordDialogData::DurationTimestamp, static_cast(timestamp.toSecsSinceEpoch())); - - info.setValue(PasswordDialogData::ProductGroup, tr(bs::network::Asset::toString(assetType()))); - info.setValue(PasswordDialogData::Security, security()); - info.setValue(PasswordDialogData::Product, product()); - info.setValue(PasswordDialogData::Side, tr(bs::network::Side::toString(side()))); - info.setValue(PasswordDialogData::ExpandTxInfo, expandTxDialogInfo_); - - return info; -} - -sync::PasswordDialogData SettlementContainer::toPayOutTxDetailsPasswordDialogData(core::wallet::TXSignRequest payOutReq - , QDateTime timestamp) const -{ - bs::sync::PasswordDialogData dialogData = toPasswordDialogData(timestamp); - - dialogData.setValue(PasswordDialogData::Title, tr("Settlement Pay-Out")); - dialogData.setValue(PasswordDialogData::DurationLeft, static_cast(kWaitTimeoutInSec * 1000)); - dialogData.setValue(PasswordDialogData::DurationTotal, static_cast(kWaitTimeoutInSec * 1000)); - dialogData.setValue(PasswordDialogData::SettlementPayOutVisible, true); - - return dialogData; -} - -void SettlementContainer::startTimer(const unsigned int durationSeconds) -{ - timer_.stop(); - timer_.setInterval(250); - msDuration_ = durationSeconds * 1000; - startTime_ = std::chrono::steady_clock::now(); - - connect(&timer_, &QTimer::timeout, [this] { - const auto timeDiff = std::chrono::duration_cast(std::chrono::steady_clock::now() - startTime_); - msTimeLeft_ = msDuration_ - timeDiff.count(); - if (msTimeLeft_ < 0) { - timer_.stop(); - msDuration_ = 0; - msTimeLeft_ = 0; - emit timerExpired(); - } - }); - timer_.start(); - emit timerStarted(msDuration_); -} - -void SettlementContainer::stopTimer() -{ - msDuration_ = 0; - msTimeLeft_ = 0; - timer_.stop(); - emit timerStopped(); -} - -void SettlementContainer::releaseUtxoRes() -{ - utxoRes_.release(); -} diff --git a/BlockSettleUILib/Trading/SettlementContainer.h b/BlockSettleUILib/Trading/SettlementContainer.h deleted file mode 100644 index 50cb87a66..000000000 --- a/BlockSettleUILib/Trading/SettlementContainer.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __SETTLEMENT_CONTAINER_H__ -#define __SETTLEMENT_CONTAINER_H__ - -#include -#include -#include -#include - -#include "ArmoryConnection.h" -#include "CommonTypes.h" -#include "CoreWallet.h" -#include "EncryptionUtils.h" -#include "PasswordDialogData.h" -#include "UtxoReservationToken.h" -#include "ValidityFlag.h" -#include "BSErrorCode.h" - -namespace bs { - - class SettlementContainer : public QObject - { - Q_OBJECT - public: - explicit SettlementContainer(bs::UtxoReservationToken - , std::unique_ptr walletPurpose - , bool expandTxDialogInfo); - ~SettlementContainer() override; - - virtual bool cancel() = 0; - - virtual void activate() = 0; - virtual void deactivate() = 0; - - virtual std::string id() const = 0; - virtual bs::network::Asset::Type assetType() const = 0; - virtual std::string security() const = 0; - virtual std::string product() const = 0; - virtual bs::network::Side::Type side() const = 0; - virtual double quantity() const = 0; - virtual double price() const = 0; - virtual double amount() const = 0; - - int durationMs() const { return msDuration_; } - int timeLeftMs() const { return msTimeLeft_; } - - virtual bs::sync::PasswordDialogData toPasswordDialogData(QDateTime timestamp) const; - virtual bs::sync::PasswordDialogData toPayOutTxDetailsPasswordDialogData(bs::core::wallet::TXSignRequest payOutReq - , QDateTime timestamp) const; - - static constexpr unsigned int kWaitTimeoutInSec = 30; - - signals: - void error(const std::string &id, bs::error::ErrorCode, QString); - - void completed(const std::string &id); - void failed(const std::string &id); - - void timerExpired(); - void timerStarted(int msDuration); - void timerStopped(); - - protected slots: - void startTimer(const unsigned int durationSeconds); - void stopTimer(); - - protected: - void releaseUtxoRes(); - - ValidityFlag validityFlag_; - bs::UtxoReservationToken utxoRes_; - std::unique_ptr walletPurpose_; - bool expandTxDialogInfo_{}; - - private: - QTimer timer_; - int msDuration_ = 0; - int msTimeLeft_ = 0; - std::chrono::steady_clock::time_point startTime_; - }; - -} // namespace bs - -#endif // __SETTLEMENT_CONTAINER_H__ diff --git a/BlockSettleUILib/Trading/WalletShieldBase.cpp b/BlockSettleUILib/Trading/WalletShieldBase.cpp deleted file mode 100644 index d5dd7cfc2..000000000 --- a/BlockSettleUILib/Trading/WalletShieldBase.cpp +++ /dev/null @@ -1,262 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "WalletShieldBase.h" - -#include - -#include "AuthAddressManager.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncWalletsManager.h" -#include "ApplicationSettings.h" - -#include "ui_WalletShieldPage.h" - - -namespace { - // Label texts - const QString shieldCreateCCWallet = QObject::tr("Create %1 wallet"); - const QString shieldCreateAuthLeaf = QObject::tr("Create Authentication wallet"); - const QString shieldGenerateAuthAddress = QObject::tr("Generate an Authentication Address"); - - const QString shieldCreateWallet = QObject::tr("To %1 in XBT related products, you require a wallet"); - const QString shieldPromoteToPrimary = QObject::tr("To %1 in XBT related products, you're required to have a wallet which" \ - " can contain the paths required for correctly sorting your tokens and holding" \ - " your Authentication Address(es)"); - - const QString shieldAuthValidationProcessHeader = QObject::tr("Authentication Address Validation Process"); - const QString shieldAuthValidationProcessText = QObject::tr("Your Authentication Address has been submitted.\n\n" - "BlockSettle validates the public key against the UserID and executes a transaction from it's Validation Address sometime within a %1 cycle.\n\n" - "Once executed the Authentication Address need 6 blockchain confirmations to be considered as valid" - ); - - // Button texts - const QString shieldButtonPromote = QObject::tr("Promote"); - const QString shieldButtonCreate = QObject::tr("Create"); - const QString shieldButtonGenerate = QObject::tr("Generate"); - const QString shieldButtonView = QObject::tr("View"); -} - -WalletShieldBase::WalletShieldBase(QWidget *parent) : - QWidget(parent) , - ui_(new Ui::WalletShieldPage()) -{ - ui_->setupUi(this); -} - -WalletShieldBase::~WalletShieldBase() noexcept = default; - -void WalletShieldBase::setShieldButtonAction(std::function&& action, bool oneShot) -{ - ui_->shieldButton->disconnect(); - connect(ui_->shieldButton, &QPushButton::clicked, this, [act = std::move(action), this, oneShot]() { - if (oneShot) { - ui_->shieldButton->setDisabled(true); - } - act(); - }); -} - -void WalletShieldBase::init(const std::shared_ptr &walletsManager, - const std::shared_ptr &authMgr, const std::shared_ptr &appSettings) -{ - walletsManager_ = walletsManager; - authMgr_ = authMgr; - appSettings_ = appSettings; -} - -void WalletShieldBase::setTabType(QString&& tabType) -{ - tabType_ = std::move(tabType); -} - -bool WalletShieldBase::checkWalletSettings(WalletShieldBase::ProductType productType, const QString& product) -{ - // No primary wallet - bool hasFullWallet = false; - if (walletsManager_) { - for (const auto &wallet : walletsManager_->hdWallets()) { - if (wallet->isFullWallet()) { - hasFullWallet = true; - break; - } - } - } - - if (!walletsManager_ || (!walletsManager_->hasPrimaryWallet() && !hasFullWallet)) { - showShieldCreateWallet(); - setShieldButtonAction([this]() { - emit requestPrimaryWalletCreation(); - }); - return true; - } - - if (!walletsManager_->hasPrimaryWallet()) { - assert(hasFullWallet); - showShieldPromoteToPrimaryWallet(); - setShieldButtonAction([this]() { - emit requestPrimaryWalletCreation(); - }); - return true; - } - - if (productType == ProductType::SpotXBT) { - if (walletsManager_->getAuthWallet()) { - const bool isNoVerifiedAddresses = authMgr_->GetSubmittedAddressList().empty(); - if (isNoVerifiedAddresses && authMgr_->isAtLeastOneAwaitingVerification()) - { - showShieldAuthValidationProcess(); - setShieldButtonAction([this]() { - emit authMgr_->AuthWalletCreated({}); - }); - return true; - } - else if (isNoVerifiedAddresses) { - showShieldGenerateAuthAddress(); - setShieldButtonAction([this]() { - emit authMgr_->AuthWalletCreated({}); - }); - return true; - } - } - else { - showShield(shieldCreateAuthLeaf, shieldButtonCreate); - setShieldButtonAction([this]() { - walletsManager_->createAuthLeaf(nullptr); - }); - return true; - } - } else if (!walletsManager_->getCCWallet(product.toStdString())) { - showShieldCreateLeaf(product); - setShieldButtonAction([this, product]() { - walletsManager_->CreateCCLeaf(product.toStdString()); - }); - return true; - } - - return false; -} - -WalletShieldBase::ProductType WalletShieldBase::getProductGroup(const QString &productGroup) -{ - if (productGroup == QLatin1String("Private Market")) { - return ProductType::PrivateMarket; - } - else if (productGroup == QLatin1String("Spot XBT")) { - return ProductType::SpotXBT; - } - else if (productGroup == QLatin1String("Spot FX")) { - return ProductType::SpotFX; - } -#ifndef QT_NO_DEBUG - // You need to add logic for new Product group type - Q_ASSERT(false); -#endif - return ProductType::Undefined; -} - -void WalletShieldBase::showShield(const QString& labelText, - const QString& buttonText /*= QLatin1String()*/, const QString& headerText /*= QLatin1String()*/) -{ - ui_->shieldText->setText(labelText); - - const bool isShowButton = !buttonText.isEmpty(); - ui_->shieldButton->setVisible(isShowButton); - ui_->shieldButton->setEnabled(isShowButton); - ui_->shieldButton->setText(buttonText); - - ui_->shieldHeaderText->setVisible(!headerText.isEmpty()); - ui_->shieldHeaderText->setText(headerText); - - ui_->secondInfoBlock->hide(); - ui_->thirdInfoBlock->hide(); - - raiseInStack(); -} - -void WalletShieldBase::showTwoBlockShield(const QString& headerText1, const QString& labelText1, - const QString& headerText2, const QString& labelText2) -{ - ui_->shieldButton->setVisible(false); - - ui_->shieldHeaderText->setText(headerText1); - ui_->shieldText->setText(labelText1); - ui_->shieldHeaderText->setVisible(true); - - ui_->shieldHeaderTextSecond->setText(headerText2); - ui_->shieldTextSecond->setText(labelText2); - ui_->secondInfoBlock->setVisible(true); - - ui_->thirdInfoBlock->hide(); - raiseInStack(); -} - -void WalletShieldBase::showThreeBlockShield(const QString& headerText1, const QString& labelText1, - const QString& headerText2, const QString& labelText2, - const QString& headerText3, const QString& labelText3) -{ - ui_->shieldButton->setVisible(false); - - ui_->shieldHeaderText->setText(headerText1); - ui_->shieldText->setText(labelText1); - ui_->shieldHeaderText->setVisible(true); - - ui_->shieldHeaderTextSecond->setText(headerText2); - ui_->shieldTextSecond->setText(labelText2); - ui_->secondInfoBlock->setVisible(true); - - ui_->shieldHeaderTextThird->setText(headerText3); - ui_->shieldTextThird->setText(labelText3); - ui_->thirdInfoBlock->setVisible(true); - - raiseInStack(); -} - -void WalletShieldBase::raiseInStack() -{ - QStackedWidget* stack = qobject_cast(parent()); - - // We expected that shield widget will leave only under stack widget - assert(stack); - if (!stack) { - return; - } - - stack->setCurrentWidget(this); -} - -void WalletShieldBase::showShieldPromoteToPrimaryWallet() -{ - showShield(shieldPromoteToPrimary.arg(tabType_), shieldButtonPromote); -} - -void WalletShieldBase::showShieldCreateWallet() -{ - showShield(shieldCreateWallet.arg(tabType_), shieldButtonCreate); -} - -void WalletShieldBase::showShieldCreateLeaf(const QString& product) -{ - showShield(shieldCreateCCWallet.arg(product), shieldButtonCreate); -} - -void WalletShieldBase::showShieldGenerateAuthAddress() -{ - showShield(shieldGenerateAuthAddress, shieldButtonGenerate); -} - -void WalletShieldBase::showShieldAuthValidationProcess() -{ - const bool isProd = appSettings_->get(ApplicationSettings::envConfiguration) == - static_cast(ApplicationSettings::EnvConfiguration::Production); - - showShield(shieldAuthValidationProcessText.arg(isProd ? tr("24h") : tr("15 minutes")), - shieldButtonView, shieldAuthValidationProcessHeader); -} diff --git a/BlockSettleUILib/Trading/WalletShieldBase.h b/BlockSettleUILib/Trading/WalletShieldBase.h deleted file mode 100644 index b32913d27..000000000 --- a/BlockSettleUILib/Trading/WalletShieldBase.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef WALLETSHIELDBASE_H -#define WALLETSHIELDBASE_H - -#include -#include -#include -#include "CommonTypes.h" - -namespace Ui { - class WalletShieldPage; -} -class AuthAddressManager; -namespace bs { - namespace sync { - class WalletsManager; - } -} -class ApplicationSettings; - -class WalletShieldBase : public QWidget -{ - Q_OBJECT - -public: - explicit WalletShieldBase(QWidget *parent = nullptr); - virtual ~WalletShieldBase() noexcept; - - void setShieldButtonAction(std::function&& action, bool oneShot = true); - - void init(const std::shared_ptr &walletsManager - , const std::shared_ptr &authMgr - , const std::shared_ptr &appSettings); - - using ProductType = bs::network::Asset::Type; - bool checkWalletSettings(ProductType productType, const QString &product); - static ProductType getProductGroup(const QString &productGroup); - void setTabType(QString&& tabType); - -signals: - void requestPrimaryWalletCreation(); - void loginRequested(); - -protected: - void showShield(const QString& labelText, - const QString& ButtonText = QLatin1String(), const QString& headerText = QLatin1String()); - - void showTwoBlockShield(const QString& headerText1, const QString& labelText1, - const QString& headerText2, const QString& labelText2); - - void showThreeBlockShield(const QString& headerText1, const QString& labelText1, - const QString& headerText2, const QString& labelText2, - const QString& headerText3, const QString& labelText3); - - void raiseInStack(); - - void showShieldPromoteToPrimaryWallet(); - void showShieldCreateWallet(); - void showShieldCreateLeaf(const QString& product); - void showShieldGenerateAuthAddress(); - void showShieldAuthValidationProcess(); - -protected: - std::unique_ptr ui_; - std::shared_ptr walletsManager_; - std::shared_ptr authMgr_; - std::shared_ptr appSettings_; - - QString tabType_; -}; - -#endif // WALLETSHIELDBASE_H diff --git a/BlockSettleUILib/Trading/WalletShieldPage.ui b/BlockSettleUILib/Trading/WalletShieldPage.ui deleted file mode 100644 index 3841218b0..000000000 --- a/BlockSettleUILib/Trading/WalletShieldPage.ui +++ /dev/null @@ -1,422 +0,0 @@ - - - - WalletShieldPage - - - - 0 - 0 - 402 - 406 - - - - Form - - - - - - Qt::Vertical - - - - 361 - 94 - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 10 - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - - 10 - 75 - true - - - - false - - - Header Text - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - false - - - <First block body text> - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 10 - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - - 10 - 75 - true - - - - false - - - Header Text - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - true - - - <Second block body text> - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 10 - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - - 10 - 75 - true - - - - false - - - Header Text - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - - 0 - 25 - - - - - 16777215 - 16777215 - - - - true - - - <Third block body text> - - - Qt::AlignCenter - - - true - - - true - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 100 - 25 - - - - - 100 - 35 - - - - <Unknown> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 361 - 94 - - - - - - - - - diff --git a/BlockSettleUILib/TransactionDetailDialog.cpp b/BlockSettleUILib/TransactionDetailDialog.cpp index 40d3b3599..710728c63 100644 --- a/BlockSettleUILib/TransactionDetailDialog.cpp +++ b/BlockSettleUILib/TransactionDetailDialog.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -28,217 +28,73 @@ #include -TransactionDetailDialog::TransactionDetailDialog(const TransactionPtr &tvi - , const std::shared_ptr &walletsManager - , const std::shared_ptr &armory, QWidget* parent) - : QDialog(parent) - , ui_(new Ui::TransactionDetailDialog()) - , walletsManager_(walletsManager) +TransactionDetailDialog::TransactionDetailDialog(const TransactionPtr &txi + , QWidget* parent) + : QDialog(parent) + , ui_(new Ui::TransactionDetailDialog()) { ui_->setupUi(this); itemSender_ = new QTreeWidgetItem(QStringList(tr("Sender"))); itemReceiver_ = new QTreeWidgetItem(QStringList(tr("Receiver"))); - const auto &cbInit = [this, armory, handle = validityFlag_.handle()](const TransactionPtr &item) mutable { - ValidityGuard guard(handle); - if (!handle.isValid()) { - return; - } - ui_->labelAmount->setText(item->amountStr); - ui_->labelDirection->setText(tr(bs::sync::Transaction::toString(item->direction))); - ui_->labelAddress->setText(item->mainAddress); + ui_->labelAmount->setText(txi->amountStr); + ui_->labelDirection->setText(tr(bs::sync::Transaction::toString(txi->direction))); + ui_->labelAddress->setText(txi->mainAddress); - if (item->confirmations > 0) { - ui_->labelHeight->setText(QString::number(item->txEntry.blockNum)); - } - else { - if (item->txEntry.isRBF) { - ui_->labelFlag->setText(tr("RBF eligible")); - } else if (item->isCPFP) { - ui_->labelFlag->setText(tr("CPFP eligible")); - } + if (txi->confirmations > 0) { + ui_->labelHeight->setText(QString::number(txi->txEntry.blockNum)); + } else { + if (txi->txEntry.isRBF) { + ui_->labelFlag->setText(tr("RBF eligible")); + } else if (txi->isCPFP) { + ui_->labelFlag->setText(tr("CPFP eligible")); } + } - if (item->tx.isInitialized()) { - ui_->labelSize->setText(QString::number(item->tx.getTxWeight())); - - std::set txHashSet; - std::map> txOutIndices; - - for (size_t i = 0; i < item->tx.getNumTxIn(); ++i) { - TxIn in = item->tx.getTxInCopy(i); - OutPoint op = in.getOutPoint(); - txHashSet.insert(op.getTxHash()); - txOutIndices[op.getTxHash()].insert(op.getTxOutIndex()); - } - - auto cbTXs = [this, item, txOutIndices, handle] - (const AsyncClient::TxBatchResult &txs, std::exception_ptr exPtr) mutable - { - ValidityGuard guard(handle); - if (!handle.isValid()) { - return; - } - - if (exPtr != nullptr) { - ui_->labelComment->setText(tr("Failed to get TX details")); - } - - ui_->treeAddresses->addTopLevelItem(itemSender_); - ui_->treeAddresses->addTopLevelItem(itemReceiver_); - - for (const auto &wallet : item->wallets) { - if (wallet->type() == bs::core::wallet::Type::ColorCoin) { - ccLeaf_ = std::dynamic_pointer_cast(wallet); - break; - } - } - - if (!ccLeaf_) { - for (size_t i = 0; i < item->tx.getNumTxOut(); ++i) { - const TxOut out = item->tx.getTxOutCopy(i); - const auto txType = out.getScriptType(); - if (txType == TXOUT_SCRIPT_OPRETURN || txType == TXOUT_SCRIPT_NONSTANDARD) { - continue; - } - const auto addr = bs::Address::fromTxOut(out); - const auto addressWallet = walletsManager_->getWalletByAddress(addr); - if (addressWallet && (addressWallet->type() == bs::core::wallet::Type::ColorCoin)) { - ccLeaf_ = std::dynamic_pointer_cast(addressWallet); - break; - } - } - } - if (ccLeaf_) { - ui_->labelFlag->setText(tr("CC: %1").arg(ccLeaf_->displaySymbol())); - } - - uint64_t value = 0; - bool initialized = true; - - std::set inputWallets; - - const bool isInternalTx = item->direction == bs::sync::Transaction::Internal; - for (const auto &prevTx : txs) { - if (!prevTx.second || !prevTx.second->isInitialized()) { - continue; - } - const auto &itTxOut = txOutIndices.find(prevTx.first); - if (itTxOut == txOutIndices.end()) { - continue; - } - for (const auto &txOutIdx : itTxOut->second) { - TxOut prevOut = prevTx.second->getTxOutCopy(txOutIdx); - value += prevOut.getValue(); - const auto addr = bs::Address::fromTxOut(prevOut); - const auto addressWallet = walletsManager_->getWalletByAddress(addr); - if (addressWallet) { - const auto &rootWallet = walletsManager_->getHDRootForLeaf(addressWallet->walletId()); - if (rootWallet) { - auto xbtGroup = rootWallet->getGroup(rootWallet->getXBTGroupType()); - bool isXbtLeaf = false; - - if (xbtGroup) { - const auto &xbtLeaves = xbtGroup->getLeaves(); - for (const auto &leaf : xbtLeaves) { - if (*leaf == *addressWallet) { - isXbtLeaf = true; - break; - } - } - - if (isXbtLeaf) { - inputWallets.insert(xbtLeaves.cbegin(), xbtLeaves.cend()); - } - } - - if (!isXbtLeaf) { - inputWallets.insert(addressWallet); - } - } - else { - inputWallets.insert(addressWallet); - } - if (!ccLeaf_ && (addressWallet->type() == bs::core::wallet::Type::ColorCoin)) { - ccLeaf_ = std::dynamic_pointer_cast(addressWallet); - } - } - addAddress(prevOut, false, prevTx.first, {}); - } - } + ui_->treeAddresses->addTopLevelItem(itemSender_); + ui_->treeAddresses->addTopLevelItem(itemReceiver_); + ui_->labelComment->setText(txi->comment); - std::vector allOutputs; - for (size_t i = 0; i < item->tx.getNumTxOut(); ++i) { - const TxOut out = item->tx.getTxOutCopy(i); - value -= out.getValue(); - allOutputs.push_back(out); - } - - for (size_t i = 0; i < item->tx.getNumTxOut(); ++i) { - const TxOut out = item->tx.getTxOutCopy(i); - addAddress(out, true, item->tx.getThisHash(), inputWallets - , allOutputs); - } - - if (!item->wallets.empty()) { - std::string comment; - for (const auto &wallet : item->wallets) { - comment = wallet->getTransactionComment(item->tx.getThisHash()); - if (!comment.empty()) { - break; - } - } - ui_->labelComment->setText(QString::fromStdString(comment)); - } + if (txi->tx.isInitialized()) { + ui_->labelSize->setText(QString::number(txi->tx.getTxWeight())); + } - if (initialized) { - ui_->labelFee->setText(UiUtils::displayAmount(value)); - ui_->labelSb->setText( - QString::number((float)value / (float)item->tx.getTxWeight())); - } + int64_t value = 0; + for (const auto &addrDet : txi->inputAddresses) { + addInputAddress(addrDet); + value += addrDet.value; + } + if (!txi->changeAddress.address.empty()) { + addChangeAddress(txi->changeAddress); + value -= txi->changeAddress.value; + } + for (const auto &addrDet : txi->outputAddresses) { + addOutputAddress(addrDet); + value -= addrDet.value; + } - ui_->treeAddresses->expandItem(itemSender_); - ui_->treeAddresses->expandItem(itemReceiver_); + ui_->labelFee->setText(UiUtils::displayAmount(value)); + ui_->labelSb->setText( + QString::number((float)value / (float)txi->tx.getTxWeight())); - for (int i = 0; i < ui_->treeAddresses->columnCount(); ++i) { - ui_->treeAddresses->resizeColumnToContents(i); - ui_->treeAddresses->setColumnWidth(i, - ui_->treeAddresses->columnWidth(i) + extraTreeWidgetColumnMargin); - } - adjustSize(); - }; - if (txHashSet.empty()) { - cbTXs({}, nullptr); - } - else { - armory->getTXsByHash(txHashSet, cbTXs, true); - } - } + for (int i = 0; i < ui_->treeAddresses->columnCount(); ++i) { + ui_->treeAddresses->resizeColumnToContents(i); + ui_->treeAddresses->setColumnWidth(i, + ui_->treeAddresses->columnWidth(i) + extraTreeWidgetColumnMargin); + } - ui_->labelConfirmations->setText(QString::number(item->confirmations)); - }; - TransactionsViewItem::initialize(tvi, armory.get(), walletsManager, cbInit); + ui_->treeAddresses->expandItem(itemSender_); + ui_->treeAddresses->expandItem(itemReceiver_); + adjustSize(); - bool bigEndianHash = true; - ui_->labelHash->setText(QString::fromStdString(tvi->txEntry.txHash.toHexStr(bigEndianHash))); - ui_->labelTime->setText(UiUtils::displayDateTime(QDateTime::fromTime_t(tvi->txEntry.txTime))); + ui_->labelConfirmations->setText(QString::number(txi->confirmations)); - ui_->labelWalletName->setText(tvi->walletName.isEmpty() ? tr("Unknown") : tvi->walletName); + const bool bigEndianHash = true; + ui_->labelHash->setText(QString::fromStdString(txi->txEntry.txHash.toHexStr(bigEndianHash))); + ui_->labelTime->setText(UiUtils::displayDateTime(QDateTime::fromTime_t(txi->txEntry.txTime))); - /* disabled the context menu for copy to clipboard functionality, it can be removed later - ui_->treeAddresses->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui_->treeAddresses, &QTreeView::customContextMenuRequested, [=](const QPoint& p) { - const auto address = ui_->treeAddresses->itemAt(p)->data(0, Qt::UserRole).toString(); + ui_->labelWalletName->setText(txi->walletName.isEmpty() ? tr("Unknown") : txi->walletName); - if (!address.isEmpty()) { - QMenu* menu = new QMenu(this); - QAction* copyAction = menu->addAction(tr("&Copy Address")); - connect(copyAction, &QAction::triggered, [=]() { - qApp->clipboard()->setText(address); - }); - menu->popup(ui_->treeAddresses->mapToGlobal(p)); - } - });*/ // allow address column to be copied to clipboard with right click ui_->treeAddresses->copyToClipboardColumns_.append(2); @@ -328,19 +184,7 @@ void TransactionDetailDialog::addAddress(TxOut out // can't use const ref due walletName = QString::fromStdString(addressWallet->name()); } else { - bool isCCaddress = false; - if (ccLeaf_) { - if (walletsManager_->isValidCCOutpoint(ccLeaf_->shortName(), txHash, out.getIndex(), out.getValue())) { - isCCaddress = true; - } - } - if (isCCaddress) { - valueStr += ccLeaf_->displayTxValue(int64_t(out.getValue())); - walletName = QString::fromStdString(ccLeaf_->shortName()); - } - else { - valueStr += UiUtils::displayAmount(out.getValue()); - } + valueStr += UiUtils::displayAmount(out.getValue()); } QStringList items; items << addressType; @@ -363,6 +207,66 @@ void TransactionDetailDialog::addAddress(TxOut out // can't use const ref due item->addChild(txItem); } +void TransactionDetailDialog::addInputAddress(const bs::sync::AddressDetails &addrDet) +{ + const auto &addressType = tr("Input"); + const auto &displayedAddress = QString::fromStdString(addrDet.address.display()); + const auto &valueStr = QString::fromStdString(addrDet.valueStr); + QStringList items; + items << addressType << valueStr << displayedAddress << QString::fromStdString(addrDet.walletName); + + auto item = new QTreeWidgetItem(items); + item->setData(0, Qt::UserRole, displayedAddress); + item->setData(1, Qt::UserRole, (qulonglong)addrDet.value); + itemSender_->addChild(item); + const auto &txHashStr = QString::fromStdString(fmt::format("{}/{}" + , addrDet.outHash.toHexStr(true), addrDet.outIndex)); + auto txItem = new QTreeWidgetItem(QStringList() << getScriptType(addrDet.type) + << QString::number(addrDet.value) << txHashStr); + txItem->setData(0, Qt::UserRole, txHashStr); + item->addChild(txItem); +} + +void TransactionDetailDialog::addChangeAddress(const bs::sync::AddressDetails &addrDet) +{ + const auto &addressType = tr("Change"); + const auto &displayedAddress = QString::fromStdString(addrDet.address.display()); + const auto &valueStr = QString::fromStdString(addrDet.valueStr); + QStringList items; + items << addressType << valueStr << displayedAddress << QString::fromStdString(addrDet.walletName); + + auto item = new QTreeWidgetItem(items); + item->setData(0, Qt::UserRole, displayedAddress); + item->setData(1, Qt::UserRole, (qulonglong)addrDet.value); + itemSender_->addChild(item); + const auto &txHashStr = QString::fromStdString(fmt::format("{}/{}" + , addrDet.outHash.toHexStr(true), addrDet.outIndex)); + auto txItem = new QTreeWidgetItem(QStringList() << getScriptType(addrDet.type) + << QString::number(addrDet.value) << txHashStr); + txItem->setData(0, Qt::UserRole, txHashStr); + item->addChild(txItem); +} + +void TransactionDetailDialog::addOutputAddress(const bs::sync::AddressDetails &addrDet) +{ + const auto &addressType = tr("Output"); + const auto &displayedAddress = QString::fromStdString(addrDet.address.display()); + const auto &valueStr = QString::fromStdString(addrDet.valueStr); + QStringList items; + items << addressType << valueStr << displayedAddress << QString::fromStdString(addrDet.walletName); + + auto item = new QTreeWidgetItem(items); + item->setData(0, Qt::UserRole, displayedAddress); + item->setData(1, Qt::UserRole, (qulonglong)addrDet.value); + itemReceiver_->addChild(item); + const auto &txHashStr = QString::fromStdString(fmt::format("{}/{}" + , addrDet.outHash.toHexStr(true), addrDet.outIndex)); + auto txItem = new QTreeWidgetItem(QStringList() << getScriptType(addrDet.type) + << QString::number(addrDet.value) << txHashStr); + txItem->setData(0, Qt::UserRole, txHashStr); + item->addChild(txItem); +} + QString TransactionDetailDialog::getScriptType(const TxOut &out) { switch (out.getScriptType()) { @@ -378,3 +282,19 @@ QString TransactionDetailDialog::getScriptType(const TxOut &out) } return tr("unknown"); } + +QString TransactionDetailDialog::getScriptType(TXOUT_SCRIPT_TYPE scrType) +{ + switch (scrType) { + case TXOUT_SCRIPT_STDHASH160: return tr("hash160"); + case TXOUT_SCRIPT_STDPUBKEY65: return tr("pubkey65"); + case TXOUT_SCRIPT_STDPUBKEY33: return tr("pubkey33"); + case TXOUT_SCRIPT_MULTISIG: return tr("multisig"); + case TXOUT_SCRIPT_P2SH: return tr("p2sh"); + case TXOUT_SCRIPT_NONSTANDARD: return tr("non-std"); + case TXOUT_SCRIPT_P2WPKH: return tr("p2wpkh"); + case TXOUT_SCRIPT_P2WSH: return tr("p2wsh"); + case TXOUT_SCRIPT_OPRETURN: return tr("op-return"); + } + return tr("unknown"); +} diff --git a/BlockSettleUILib/TransactionDetailDialog.h b/BlockSettleUILib/TransactionDetailDialog.h index 610a33d99..bbcfb66c6 100644 --- a/BlockSettleUILib/TransactionDetailDialog.h +++ b/BlockSettleUILib/TransactionDetailDialog.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -42,8 +42,7 @@ class TransactionDetailDialog : public QDialog Q_OBJECT public: - TransactionDetailDialog(const TransactionPtr &tvi, const std::shared_ptr & - , const std::shared_ptr &, QWidget* parent = nullptr); + TransactionDetailDialog(const TransactionPtr &, QWidget* parent = nullptr); ~TransactionDetailDialog() override; virtual QSize minimumSizeHint() const override; QSize minimumSize() const; @@ -53,10 +52,14 @@ Q_OBJECT private: using WalletsSet = std::set>; - void addAddress(TxOut out, bool isOutput + [[deprecated]] void addAddress(TxOut out, bool isOutput , const BinaryData& txHash, const WalletsSet &inputWallets , const std::vector &allOutputs = {}); - QString getScriptType(const TxOut &); + [[deprecated]] QString getScriptType(const TxOut &); + void addInputAddress(const bs::sync::AddressDetails &); + void addOutputAddress(const bs::sync::AddressDetails &); + void addChangeAddress(const bs::sync::AddressDetails &); + QString getScriptType(TXOUT_SCRIPT_TYPE); private: std::unique_ptr ui_; @@ -64,7 +67,6 @@ Q_OBJECT QTreeWidgetItem *itemSender_ = nullptr; QTreeWidgetItem *itemReceiver_ = nullptr; ValidityFlag validityFlag_; - std::shared_ptr ccLeaf_; }; #endif // __TRANSACTION_DETAIL_DIALOG_H__ diff --git a/BlockSettleUILib/TransactionDetailsWidget.cpp b/BlockSettleUILib/TransactionDetailsWidget.cpp index 80c551cad..7cf9d920e 100644 --- a/BlockSettleUILib/TransactionDetailsWidget.cpp +++ b/BlockSettleUILib/TransactionDetailsWidget.cpp @@ -20,7 +20,7 @@ #include #include -using namespace ArmorySigner; +using namespace Armory::Signer; Q_DECLARE_METATYPE(Tx); @@ -55,19 +55,9 @@ TransactionDetailsWidget::TransactionDetailsWidget(QWidget *parent) : TransactionDetailsWidget::~TransactionDetailsWidget() = default; -// Initialize the widget and related widgets (block, address, Tx) -void TransactionDetailsWidget::init( - const std::shared_ptr &armory - , const std::shared_ptr &inLogger - , const std::shared_ptr &walletsMgr - , const std::shared_ptr &resolver) +void TransactionDetailsWidget::init(const std::shared_ptr &logger) { - armoryPtr_ = armory; - logger_ = inLogger; - walletsMgr_ = walletsMgr; - ccResolver_ = resolver; - act_ = make_unique(this); - act_->init(armoryPtr_.get()); + logger_ = logger; } // This function uses getTxByHash() to retrieve info about transaction. The @@ -79,6 +69,38 @@ void TransactionDetailsWidget::populateTransactionWidget(const TxHash &rpcTXID if (firstPass) { clear(); } + + if (!armoryPtr_) { + if (rpcTXID.getSize() == 32) { + curTxHash_ = rpcTXID; + emit needTXDetails({ { curTxHash_, {}, 0 } }, false, {}); + } + else { + Codec_SignerState::SignerState signerState; + if (signerState.ParseFromString(rpcTXID.toBinStr(true))) { + Signer signer(signerState); + const auto& serTx = signer.serializeUnsignedTx(true); + try { + const Tx tx(serTx); + if (!tx.isInitialized()) { + throw std::runtime_error("Uninited TX"); + } + curTxHash_ = tx.getThisHash(); + emit needTXDetails({ { tx.getThisHash(), {}, 0 } }, false, {}); + } + catch (const std::exception& e) { + logger_->error("[TransactionDetailsWidget::populateTransactionWidget]" + " {}", e.what()); + } + } + else { + logger_->error("[TransactionDetailsWidget::populateTransactionWidget]" + " failed to decode signer state"); + } + } + return; + } + // get the transaction data from armory const auto txidStr = rpcTXID.getRPCTXID(); const auto cbTX = [this, txidStr](const Tx &tx) { @@ -96,10 +118,6 @@ void TransactionDetailsWidget::populateTransactionWidget(const TxHash &rpcTXID if (firstPass || !curTx_.isInitialized() || (curTx_.getThisHash() != rpcTXID)) { if (rpcTXID.getSize() == 32) { - if (!armoryPtr_) { - logger_->error("[TransactionDetailsWidget::populateTransactionWidget] Armory is not inited"); - return; - } if (!armoryPtr_->getTxByHash(rpcTXID, cbTX, false)) { if (logger_) { logger_->error("[TransactionDetailsWidget::populateTransactionWidget]" @@ -135,6 +153,110 @@ void TransactionDetailsWidget::populateTransactionWidget(const TxHash &rpcTXID } } +void TransactionDetailsWidget::onTXDetails(const std::vector &txDet) +{ + if (txDet.empty()) { + return; + } + if ((txDet.size() > 1) || (!txDet.empty() && !txDet[0].txHash.empty() && (txDet[0].txHash != curTxHash_))) { + logger_->debug("[{}] not our TX details", __func__); + return; // not our data + } + if ((txDet[0].txHash.empty() || !txDet[0].tx.getSize()) && !txDet[0].comment.empty()) { + ui_->tranID->setText(QString::fromStdString(txDet[0].comment)); + return; + } + curTx_ = txDet[0].tx; + if (!curTx_.isInitialized()) { + ui_->tranID->setText(tr("Loading...")); + return; + } + ui_->tranID->setText(QString::fromStdString(curTx_.getThisHash().toHexStr(true))); + emit finished(); + + const auto txHeight = curTx_.getTxHeight(); + ui_->nbConf->setVisible(txHeight != UINT32_MAX); + ui_->labelNbConf->setVisible(txHeight != UINT32_MAX); + const uint32_t nbConf = topBlock_ ? topBlock_ + 1 - txHeight : 0; + ui_->nbConf->setText(QString::number(nbConf)); + + if (txDet.empty()) { + return; + } + // Get fees & fee/byte by looping through the prev Tx set and calculating. + uint64_t totIn = 0; + for (const auto& inAddr : txDet[0].inputAddresses) { + totIn += inAddr.value; + } + + uint64_t fees = totIn - curTx_.getSumOfOutputs(); + float feePerByte = (float)fees / (float)curTx_.getTxWeight(); + ui_->tranInput->setText(UiUtils::displayAmount(totIn)); + ui_->tranFees->setText(UiUtils::displayAmount(fees)); + ui_->tranFeePerByte->setText(QString::number(nearbyint(feePerByte))); + ui_->tranNumInputs->setText(QString::number(curTx_.getNumTxIn())); + ui_->tranNumOutputs->setText(QString::number(curTx_.getNumTxOut())); + ui_->tranOutput->setText(UiUtils::displayAmount(curTx_.getSumOfOutputs())); + ui_->tranSize->setText(QString::number(curTx_.getTxWeight())); + + ui_->treeInput->clear(); + ui_->treeOutput->clear(); + + std::map hashCounts; + for (const auto &inAddr : txDet[0].inputAddresses) { + hashCounts[inAddr.outHash]++; + } + + // here's the code to add data to the Input tree. + for (const auto& inAddr : txDet[0].inputAddresses) { + QString addrStr; + const QString walletName = QString::fromStdString(inAddr.walletName); + + // For now, don't display any data if the TxOut is non-std. Displaying a + // hex version of the script is one thing that could be done. This needs + // to be discussed before implementing. Non-std could mean many things. + if (inAddr.type == TXOUT_SCRIPT_NONSTANDARD) { + addrStr = tr(""); + } + else { + addrStr = QString::fromStdString(inAddr.address.display()); + } + + // create a top level item using type, address, amount, wallet values + addItem(ui_->treeInput, addrStr, inAddr.value, walletName, inAddr.outHash + , (hashCounts[inAddr.outHash] > 1) ? inAddr.outIndex : -1); + } + + std::vector outputAddresses = txDet[0].outputAddresses; + if (!txDet[0].changeAddress.address.empty()) { + outputAddresses.push_back(txDet[0].changeAddress); + } + for (const auto &outAddr : outputAddresses) { + QString addrStr; + QString walletName; + + // For now, don't display any data if the TxOut is OP_RETURN or non-std. + // Displaying a hex version of the script is one thing that could be done. + // This needs to be discussed before implementing. OP_RETURN isn't too bad + // (80 bytes max) but non-std could mean just about anything. + if (outAddr.type == TXOUT_SCRIPT_OPRETURN) { + addrStr = tr(""); + } + else if (outAddr.type == TXOUT_SCRIPT_NONSTANDARD) { + addrStr = tr(""); + } + else { + walletName = QString::fromStdString(outAddr.walletName); + addrStr = QString::fromStdString(outAddr.address.display()); + } + + addItem(ui_->treeOutput, addrStr, outAddr.value, walletName, outAddr.outHash); + } + + ui_->treeInput->resizeColumns(); + ui_->treeOutput->resizeColumns(); +} + // Used in callback to process the Tx object returned by Armory. void TransactionDetailsWidget::processTxData(const Tx &tx) { @@ -221,8 +343,13 @@ void TransactionDetailsWidget::setTxGUIValues() loadInputs(); } -void TransactionDetailsWidget::onNewBlock(unsigned int) +void TransactionDetailsWidget::onNewBlock(unsigned int curBlock) { + if (!armoryPtr_) { + topBlock_ = curBlock; + onTXDetails({}); + return; + } if (curTx_.isInitialized()) { populateTransactionWidget(curTx_.getThisHash(), false); } @@ -235,54 +362,6 @@ void TransactionDetailsWidget::loadInputs() loadTreeOut(ui_->treeOutput); } -void TransactionDetailsWidget::updateTreeCC(QTreeWidget *tree - , const std::string &cc, uint64_t lotSize) -{ - for (int i = 0; i < tree->topLevelItemCount(); ++i) { - auto item = tree->topLevelItem(i); - const uint64_t amt = item->data(1, Qt::UserRole).toULongLong(); - if (amt && ((amt % lotSize) == 0)) { - item->setData(1, Qt::DisplayRole, QString::number(amt / lotSize)); - const auto addrWallet = item->data(2, Qt::DisplayRole).toString(); - if (addrWallet.isEmpty()) { - item->setData(2, Qt::DisplayRole, QString::fromStdString(cc)); - } - for (int j = 0; j < item->childCount(); ++j) { - auto outItem = item->child(j); - const uint64_t outAmt = outItem->data(1, Qt::UserRole).toULongLong(); - outItem->setData(1, Qt::DisplayRole, QString::number(outAmt / lotSize)); - outItem->setData(2, Qt::DisplayRole, QString::fromStdString(cc)); - } - } - } -} - -void TransactionDetailsWidget::checkTxForCC(const Tx &tx, QTreeWidget *treeWidget) -{ - for (const auto &cc : ccResolver_->securities()) { - const auto &genesisAddr = ccResolver_->genesisAddrFor(cc); - auto txChecker = std::make_shared(genesisAddr, armoryPtr_); - const auto &cbHasGA = [this, txChecker, treeWidget, cc](bool found) { - if (!found) { - return; - } - QMetaObject::invokeMethod(this, [this, treeWidget, cc] { - updateTreeCC(treeWidget, cc, ccResolver_->lotSizeFor(cc)); - }); - }; - txChecker->containsInputAddress(tx, cbHasGA, ccResolver_->lotSizeFor(cc)); - } -} - -void TransactionDetailsWidget::updateCCInputs() -{ - for (int i = 0; i < curTx_.getNumTxIn(); ++i) { - const OutPoint op = curTx_.getTxInCopy(i).getOutPoint(); - const auto &prevTx = prevTxMap_[op.getTxHash()]; - checkTxForCC(*prevTx, ui_->treeInput); - } -} - // Input widget population. void TransactionDetailsWidget::loadTreeIn(CustomTreeWidget *tree) { @@ -326,8 +405,6 @@ void TransactionDetailsWidget::loadTreeIn(CustomTreeWidget *tree) , (hashCounts[intPrevTXID] > 1) ? op.getTxOutIndex() : -1); } tree->resizeColumns(); - - updateCCInputs(); } // Output widget population. @@ -362,8 +439,6 @@ void TransactionDetailsWidget::loadTreeOut(CustomTreeWidget *tree) // add the item to the tree } tree->resizeColumns(); - - checkTxForCC(curTx_, ui_->treeOutput); } void TransactionDetailsWidget::addItem(QTreeWidget *tree, const QString &address @@ -439,6 +514,7 @@ void TransactionDetailsWidget::clear() { prevTxMap_.clear(); curTx_ = Tx(); + curTxHash_.clear(); ui_->tranID->clear(); ui_->tranNumInputs->clear(); diff --git a/BlockSettleUILib/TransactionDetailsWidget.h b/BlockSettleUILib/TransactionDetailsWidget.h index c77318518..d50c97200 100644 --- a/BlockSettleUILib/TransactionDetailsWidget.h +++ b/BlockSettleUILib/TransactionDetailsWidget.h @@ -13,7 +13,7 @@ #include "ArmoryConnection.h" #include "BinaryData.h" -#include "CCFileManager.h" +#include "Wallets/SignerDefs.h" #include "TxClasses.h" #include @@ -74,10 +74,7 @@ class TransactionDetailsWidget : public QWidget explicit TransactionDetailsWidget(QWidget *parent = nullptr); ~TransactionDetailsWidget() override; - void init(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); + void init(const std::shared_ptr&); void populateTransactionWidget(const TxHash &rpcTXID, const bool& firstPass = true); @@ -88,14 +85,20 @@ class TransactionDetailsWidget : public QWidget colWallet }; + void onTXDetails(const std::vector&); + +public slots: + void onNewBlock(unsigned int blockNum); + signals: void addressClicked(QString addressId); void txHashClicked(QString txHash); void finished() const; + void needTXDetails(const std::vector&, bool useCache + , const bs::Address&); protected slots: void onAddressClicked(QTreeWidgetItem *item, int column); - void onNewBlock(unsigned int); protected: void loadTreeIn(CustomTreeWidget *tree); @@ -105,24 +108,21 @@ protected slots: void loadInputs(); void setTxGUIValues(); void clear(); - void updateCCInputs(); - void checkTxForCC(const Tx &, QTreeWidget *); void processTxData(const Tx &tx); void addItem(QTreeWidget *tree, const QString &address, const uint64_t amount , const QString &wallet, const BinaryData &txHash, const int txIndex = -1); - static void updateTreeCC(QTreeWidget *, const std::string &product, uint64_t lotSize); - private: std::unique_ptr ui_; std::shared_ptr armoryPtr_; std::shared_ptr logger_; std::shared_ptr walletsMgr_; - std::shared_ptr ccResolver_; Tx curTx_; // The Tx being analyzed in the widget. + BinaryData curTxHash_; + uint32_t topBlock_{ 0 }; // Data captured from Armory callbacks. AsyncClient::TxBatchResult prevTxMap_; // Prev Tx hash / Prev Tx map. diff --git a/BlockSettleUILib/TransactionsViewModel.cpp b/BlockSettleUILib/TransactionsViewModel.cpp index ac624ce5e..15398dae1 100644 --- a/BlockSettleUILib/TransactionsViewModel.cpp +++ b/BlockSettleUILib/TransactionsViewModel.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -171,14 +171,23 @@ void TXNode::add(TXNode *child) } void TXNode::del(int index) +{ + auto node = take(index); + if (node) { + delete node; + } +} + +TXNode* TXNode::take(int index) { if (index >= children_.size()) { - return; + return nullptr; } - children_.removeAt(index); + auto node = children_.takeAt(index); for (int i = index; i < children_.size(); ++i) { children_[i]->row_--; } + return node; } void TXNode::forEach(const std::function &cb) @@ -221,6 +230,20 @@ TXNode *TXNode::find(const bs::TXEntry &entry) const return nullptr; } +TXNode* TXNode::find(const BinaryData& txHash) const +{ + if (item_ && (item_->txEntry.txHash == txHash)) { + return const_cast(this); + } + for (const auto& child : children_) { + const auto found = child->find(txHash); + if (found != nullptr) { + return found; + } + } + return nullptr; +} + std::vector TXNode::nodesByTxHash(const BinaryData &txHash) const { std::vector result; @@ -246,100 +269,20 @@ unsigned int TXNode::level() const } -TransactionsViewModel::TransactionsViewModel(const std::shared_ptr &armory - , const std::shared_ptr &walletsManager - , const std::shared_ptr &ledgerDelegate - , const std::shared_ptr &logger - , const std::shared_ptr &defWlt - , const bs::Address &filterAddress - , QObject* parent) - : QAbstractItemModel(parent) - , logger_(logger) - , ledgerDelegate_(ledgerDelegate) - , walletsManager_(walletsManager) - , defaultWallet_(defWlt) - , allWallets_(false) - , filterAddress_(filterAddress) -{ - init(); - ArmoryCallbackTarget::init(armory.get()); - loadLedgerEntries(); -} - -TransactionsViewModel::TransactionsViewModel(const std::shared_ptr &armory - , const std::shared_ptr &walletsManager - , const std::shared_ptr &logger - , QObject* parent) - : QAbstractItemModel(parent) - , logger_(logger) - , walletsManager_(walletsManager) - , allWallets_(true) -{ - ArmoryCallbackTarget::init(armory.get()); - init(); -} - -void TransactionsViewModel::init() +TransactionsViewModel::TransactionsViewModel(const std::shared_ptr &logger + , QObject* parent) + : QAbstractItemModel(parent), logger_(logger) + , filterAddress_{} { stopped_ = std::make_shared(false); - qRegisterMetaType(); - qRegisterMetaType(); - - rootNode_.reset(new TXNode); - - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletChanged, this, &TransactionsViewModel::refresh, Qt::QueuedConnection); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletDeleted, this, &TransactionsViewModel::onWalletDeleted, Qt::QueuedConnection); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletImportFinished, this, &TransactionsViewModel::refresh, Qt::QueuedConnection); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletsReady, this, &TransactionsViewModel::updatePage, Qt::QueuedConnection); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, &TransactionsViewModel::onRefreshTxValidity, Qt::QueuedConnection); - - // Need this to be able mark invalid CC TXs in red - connect(walletsManager_.get(), &bs::sync::WalletsManager::ccTrackerReady, this, &TransactionsViewModel::onRefreshTxValidity, Qt::QueuedConnection); + rootNode_ = std::make_unique(); } TransactionsViewModel::~TransactionsViewModel() noexcept { - cleanup(); *stopped_ = true; } -void TransactionsViewModel::onNewBlock(unsigned int, unsigned int) -{ - QMetaObject::invokeMethod(this, [this] { - if (allWallets_) { - loadAllWallets(true); - } - }); -} - -void TransactionsViewModel::loadAllWallets(bool onNewBlock) -{ - const auto &cbWalletsLD = [this, onNewBlock](const std::shared_ptr &delegate) { - if (!initialLoadCompleted_) { - if (onNewBlock && logger_) { - logger_->debug("[TransactionsViewModel::loadAllWallets] previous loading is not complete, yet"); - } - return; - } - ledgerDelegate_ = delegate; - if (onNewBlock && logger_) { - logger_->debug("[TransactionsViewModel::loadAllWallets] ledger delegate is updated"); - } - loadLedgerEntries(onNewBlock); - }; - if (initialLoadCompleted_) { - if (ledgerDelegate_) { - loadLedgerEntries(onNewBlock); - } - else { - SPDLOG_LOGGER_DEBUG(logger_, "create new ledger delegate"); - armory_->getWalletsLedgerDelegate(cbWalletsLD); - } - } else { - SPDLOG_LOGGER_WARN(logger_, "initial load is not completed yet"); - } -} - int TransactionsViewModel::columnCount(const QModelIndex &) const { return static_cast(Columns::last) + 1; @@ -353,64 +296,6 @@ TXNode *TransactionsViewModel::getNode(const QModelIndex &index) const return static_cast(index.internalPointer()); } -bool TransactionsViewModel::isTxRevocable(const Tx& tx) -{ - auto iRevokable = revocableTxs_.find(tx.getThisHash()); - if (iRevokable != revocableTxs_.cend()) { - return iRevokable->second; - } - - // Output to settlement address must be first - const auto settlementOutIndex = 0; - - try { - const auto &txOut = tx.getTxOutCopy(settlementOutIndex); - const auto &addr = bs::Address::fromTxOut(txOut); - if (addr.getType() != AddressEntryType_P2WSH) { - return false; - } - } catch (...) { - SPDLOG_LOGGER_ERROR(logger_, "can't get address"); - return false; - } - - const std::map> spentnessToTrack = { { tx.getThisHash(), { static_cast(settlementOutIndex) } } }; - - auto cbStoreRevoke = [this, caller = QPointer(this), txHash = tx.getThisHash()](const std::map> &results, std::exception_ptr exPtr) { - QMetaObject::invokeMethod(qApp, [this, caller, txHash, results, exPtr] { - if (!caller) { - return; - } - - if (exPtr != nullptr) { - SPDLOG_LOGGER_ERROR(logger_, "request failed"); - return; - } - - auto iResult = results.find(txHash); - if (iResult == results.cend()) { - SPDLOG_LOGGER_ERROR(logger_, "TX not found"); - return; - } - - for (auto const &result : iResult->second) { - if (result.second.state_ == OutputSpentnessState::Unspent) { - revocableTxs_[txHash] = true; - return; - } - } - }); - }; - - armory_->getSpentnessForOutputs(spentnessToTrack, cbStoreRevoke); - armory_->getSpentnessForZcOutputs(spentnessToTrack, cbStoreRevoke); - - // We want user to have this as false while result is not returning back - revocableTxs_[tx.getThisHash()] = false; - return false; -} - int TransactionsViewModel::rowCount(const QModelIndex &parent) const { const auto &node = getNode(parent); @@ -503,24 +388,6 @@ QVariant TransactionsViewModel::headerData(int section, Qt::Orientation orientat return QVariant(); } -void TransactionsViewModel::refresh() -{ - updatePage(); -} - -void TransactionsViewModel::onWalletDeleted(std::string) -{ - clear(); - updatePage(); -} - -void TransactionsViewModel::updatePage() -{ - if (allWallets_) { - loadAllWallets(); - } -} - void TransactionsViewModel::clear() { *stopped_ = true; @@ -534,331 +401,23 @@ void TransactionsViewModel::clear() *stopped_ = false; } -void TransactionsViewModel::onStateChanged(ArmoryState state) -{ - QMetaObject::invokeMethod(this, [this, state] { - if (state == ArmoryState::Offline) { - ledgerDelegate_.reset(); - clear(); - } else if ((state == ArmoryState::Ready) && !rootNode_->hasChildren()) { - loadAllWallets(); - } - }); -} - -std::shared_ptr TransactionsViewModel::itemFromTransaction(const bs::TXEntry &entry) +std::shared_ptr TransactionsViewModel::createTxItem(const bs::TXEntry &entry) { auto item = std::make_shared(); item->txEntry = entry; item->displayDateTime = UiUtils::displayDateTime(entry.txTime); - for (const auto &walletId : entry.walletIds) { - const auto wallet = walletsManager_->getWalletById(walletId); - if (wallet) { - item->wallets.push_back(wallet); - } - } - item->filterAddress = filterAddress_; - if (item->wallets.empty() && defaultWallet_) { - item->wallets.push_back(defaultWallet_); - } - if (!item->wallets.empty()) { - item->walletID = QString::fromStdString(item->wallets[0]->walletId()); - } - else { +// item->filterAddress = filterAddress_; + if (!entry.walletIds.empty()) { item->walletID = QString::fromStdString(*entry.walletIds.cbegin()); } - - item->confirmations = armory_->getConfirmationsNumber(entry.blockNum); + item->confirmations = entry.nbConf; if (!item->wallets.empty()) { item->walletName = QString::fromStdString(item->wallets[0]->name()); } - const auto validWallet = item->wallets.empty() ? nullptr : item->wallets[0]; - item->isValid = validWallet ? validWallet->isTxValid(entry.txHash) : bs::sync::TxValidity::Invalid; + item->amountStr = UiUtils::displayAmount(entry.value); return item; } -void TransactionsViewModel::onZCReceived(const std::string& requestId, const std::vector& entries) -{ - QMetaObject::invokeMethod(this, [this, entries] { updateTransactionsPage(entries); }); -} - -void TransactionsViewModel::onZCInvalidated(const std::set &ids) -{ - std::vector delRows; -#ifdef TX_MODEL_NESTED_NODES - std::vector children; -#endif - { - QMutexLocker locker(&updateMutex_); - for (const auto &txHash : ids) { - const auto invNodes = rootNode_->nodesByTxHash(txHash); - for (const auto &node : invNodes) { - delRows.push_back(node->row()); - } -#ifdef TX_MODEL_NESTED_NODES // nested nodes are not supported for now - const auto node = rootNode_->find(entry); - if (node && (node->parent() == rootNode_.get()) && !node->item()->confirmations) { - delRows.push_back(node->row()); - if (node->hasChildren()) { // handle race condition when node being deleted has confirmed children - for (const auto &child : node->children()) { - if (child->item()->confirmations) { - children.push_back(child->item()->txEntry); - } - } - } - } -#endif //TX_MODEL_NESTED_NODES - } - } - if (!delRows.empty()) { - QMetaObject::invokeMethod(this, [this, delRows] { - onDelRows(delRows); - }); - } - -#ifdef TX_MODEL_NESTED_NODES - if (!children.empty()) { - logger_->debug("[{}] {} children to update", __func__, children.size()); - updateTransactionsPage(children); - } -#endif -} - -#ifdef TX_MODEL_NESTED_NODES -static bool isChildOf(TransactionPtr child, TransactionPtr parent) -{ - if (!child->initialized || !parent->initialized) { - return false; - } - if (!parent->parentId.empty() && !child->groupId.empty()) { - if (child->groupId == parent->parentId) { - return true; - } - } - if ((!child->confirmations && child->txEntry.isRBF && !parent->confirmations && parent->txEntry.isRBF) - && (child->txEntry.txHash != parent->txEntry.txHash) - && (child->txEntry.walletIds == parent->txEntry.walletIds)) { - std::set childInputs, parentInputs; - for (int i = 0; i < int(child->tx.getNumTxIn()); i++) { - childInputs.insert(child->tx.getTxInCopy(i).serialize()); - } - for (int i = 0; i < int(parent->tx.getNumTxIn()); i++) { - parentInputs.insert(parent->tx.getTxInCopy(i).serialize()); - } - if (childInputs == parentInputs) { - return true; - } - } - return false; -} -#endif //TX_MODEL_NESTED_NODES - -std::pair TransactionsViewModel::updateTransactionsPage(const std::vector &page) -{ - struct ItemKey { - BinaryData txHash; - std::set walletIds; - bool operator<(const ItemKey &ik) const { - if (txHash != ik.txHash) { - return (txHash < ik.txHash); - } - return (walletIds < ik.walletIds); - } - }; - auto newItems = std::make_shared>(); - auto updatedItems = std::make_shared>(); - auto newTxKeys = std::make_shared>(); - - const auto mergedPage = allWallets_ ? walletsManager_->mergeEntries(page) : page; - - const auto lbdAddNew = [this, newItems, newTxKeys](const TransactionPtr &item) - { - if (!oldestItem_ || (oldestItem_->txEntry.txTime >= item->txEntry.txTime)) { - oldestItem_ = item; - } - newTxKeys->insert({ item->txEntry.txHash, item->txEntry.walletIds }); - newItems->push_back(new TXNode(item)); - }; - - const auto mergeItem = [this, updatedItems](const TransactionPtr &item) -> bool - { - for (const auto &node : rootNode_->children()) { - if (!node) { - continue; - } - if (walletsManager_->mergeableEntries(node->item()->txEntry, item->txEntry)) { - item->txEntry.merge(node->item()->txEntry); - updatedItems->push_back(item); - return true; - } - } - return false; - }; - - for (const auto &entry : mergedPage) { - const auto item = itemFromTransaction(entry); - if (item->wallets.empty()) { - continue; - } - - TXNode *node = nullptr; - { - QMutexLocker locker(&updateMutex_); - node = rootNode_->find(item->txEntry); - } - if (node) { - updatedItems->push_back(item); - } - else { - if (allWallets_) { - if (!mergeItem(item)) { - lbdAddNew(item); - } - } - else { - lbdAddNew(item); - } - } - } - - const auto &cbInited = [this, newItems, updatedItems, newTxKeys] - (const TransactionPtr &itemPtr) - { - if (!itemPtr || !itemPtr->initialized) { - logger_->error("item is not inited"); - return; - } - if (newTxKeys->empty()) { - logger_->warn("TX keys already empty"); - return; - } - newTxKeys->erase({ itemPtr->txEntry.txHash, itemPtr->txEntry.walletIds }); - if (newTxKeys->empty()) { -#ifdef TX_MODEL_NESTED_NODES - std::unordered_set deletedItems; - if (rootNode_->hasChildren()) { - std::vector delRows; - const auto &cbEachExisting = [newItems, &delRows](TXNode *txNode) { - for (auto &newItem : *newItems) { - if (newItem.second.second->find(txNode->item()->id()) - || txNode->find(newItem.first)) { // avoid looped graphs - continue; - } - if (isChildOf(txNode->item(), newItem.second.first)) { - delRows.push_back(txNode->row()); - auto &&children = txNode->children(); - newItem.second.second->add(txNode); - for (auto &child : children) { - newItem.second.second->add(child); - } - txNode->clear(false); - } - else if (isChildOf(newItem.second.first, txNode->item())) { - // do nothing, yet - } - } - }; - { - QMutexLocker locker(&updateMutex_); - for (auto &child : rootNode_->children()) { - cbEachExisting(child); - } - } - if (!delRows.empty()) { - onDelRows(delRows); - } - } - const auto newItemsCopy = *newItems; - for (auto &parentItem : *newItems) { - if (deletedItems.find(parentItem.first) != deletedItems.end()) { // don't treat child-parent transitively - continue; - } - for (auto &childItem : newItemsCopy) { - if (parentItem.first == childItem.first) { // don't compare with self - continue; - } - if (deletedItems.find(childItem.first) != deletedItems.end()) { - continue; - } - if (isChildOf(childItem.second.first, parentItem.second.first)) { - parentItem.second.second->add(childItem.second.second); - deletedItems.insert(childItem.second.first->id()); - } - } - } - for (const auto &delId : deletedItems) { - newItems->erase(delId); - } -#endif //TX_MODEL_NESTED_NODES - if (!newItems->empty()) { - onNewItems(*newItems); - if (signalOnEndLoading_) { - signalOnEndLoading_ = false; - emit dataLoaded(int(newItems->size())); - } - } - if (!updatedItems->empty()) { - updateBlockHeight(*updatedItems); - } - } - }; - - const auto newItemsCopy = *newItems; - if (!newItemsCopy.empty()) { - for (const auto &node : newItemsCopy) { - updateTransactionDetails(node->item(), cbInited); - } - } - else { - if (!updatedItems->empty()) { - updateBlockHeight(*updatedItems); - } - emit dataLoaded(0); - } - - return { newItemsCopy.size(), updatedItems->size() }; -} - -void TransactionsViewModel::updateBlockHeight(const std::vector> &updItems) -{ - if (!rootNode_->hasChildren()) { - logger_->debug("[{}] root node doesn't have children", __func__); - return; - } - - for (const auto &updItem : updItems) { - TXNode *node = nullptr; - { - QMutexLocker locker(&updateMutex_); - node = rootNode_->find(updItem->txEntry); - } - if (!node) { - continue; - } - const auto &item = node->item(); - if (!updItem->wallets.empty()) { - item->isValid = updItem->wallets[0]->isTxValid(updItem->txEntry.txHash); - } - if (item->txEntry.value != updItem->txEntry.value) { - item->wallets = updItem->wallets; - item->walletID = updItem->walletID; - item->txEntry = updItem->txEntry; - item->amountStr.clear(); - item->calcAmount(walletsManager_); - } - const auto newBlockNum = updItem->txEntry.blockNum; - if (newBlockNum != UINT32_MAX) { - const auto confNum = armory_->getConfirmationsNumber(newBlockNum); - item->confirmations = confNum; - item->txEntry.blockNum = newBlockNum; - onItemConfirmed(item); - } - } - - emit dataChanged(index(0, static_cast(Columns::Amount)) - , index(rootNode_->nbChildren() - 1, static_cast(Columns::Status))); -} - void TransactionsViewModel::onItemConfirmed(const TransactionPtr item) { if (item->txEntry.isRBF && (item->confirmations == 1)) { @@ -885,108 +444,13 @@ void TransactionsViewModel::onRefreshTxValidity() if (item->isValid != newState) { item->isValid = newState; // Update balance in case lotSize_ is received after CC gen file loaded - item->calcAmount(walletsManager_); +// item->calcAmount(walletsManager_); emit dataChanged(index(i, static_cast(Columns::first)) , index(i, static_cast(Columns::last))); } } } -void TransactionsViewModel::loadLedgerEntries(bool onNewBlock) -{ - if (!initialLoadCompleted_ || !ledgerDelegate_) { - if (onNewBlock && logger_) { - logger_->debug("[TransactionsViewModel::loadLedgerEntries] previous loading is not complete/started"); - } - return; - } - initialLoadCompleted_ = false; - - QPointer thisPtr = this; - auto rawData = std::make_shared>>(); - auto rawDataMutex = std::make_shared(); - - const auto &cbPageCount = [thisPtr, onNewBlock, stopped = stopped_, logger = logger_, rawData, rawDataMutex, ledgerDelegate = ledgerDelegate_] - (ReturnMessage pageCnt) - { - try { - int inPageCnt = int(pageCnt.get()); - - if (inPageCnt == 0) { - SPDLOG_LOGGER_ERROR(logger, "page count is 0"); - thisPtr->initialLoadCompleted_ = true; - return; - } - - QMetaObject::invokeMethod(qApp, [thisPtr, inPageCnt] { - if (thisPtr) { - emit thisPtr->initProgress(0, int(inPageCnt * 2)); - } - }); - - for (int pageId = 0; pageId < inPageCnt; ++pageId) { - if (*stopped) { - logger->debug("[TransactionsViewModel::loadLedgerEntries] stopped"); - break; - } - - const auto &cbLedger = [thisPtr, onNewBlock, pageId, inPageCnt, rawData, logger, rawDataMutex] - (ReturnMessage> entries)->void { - try { - auto le = entries.get(); - - std::lock_guard lock(*rawDataMutex); - - (*rawData)[pageId] = bs::TXEntry::fromLedgerEntries(le); - if (onNewBlock && logger) { - logger->debug("[TransactionsViewModel::loadLedgerEntries] loaded {} entries for page {} (of {})" - , le.size(), pageId, inPageCnt); - } - - if (int(rawData->size()) >= inPageCnt) { - QMetaObject::invokeMethod(qApp, [thisPtr, rawData, onNewBlock] { - if (thisPtr) { - thisPtr->ledgerToTxData(*rawData, onNewBlock); - } - }); - } - } - catch (std::exception& e) { - logger->error("[TransactionsViewModel::loadLedgerEntries::cbLedger] " \ - "return data error: {}", e.what()); - } - - QMetaObject::invokeMethod(qApp, [thisPtr, pageId] { - if (thisPtr) { - emit thisPtr->updateProgress(pageId); - } - }); - }; - ledgerDelegate->getHistoryPage(uint32_t(pageId), cbLedger); - } - } - catch (const std::exception &e) { - logger->error("[TransactionsViewModel::loadLedgerEntries::cbPageCount] return " \ - "data error: {}", e.what()); - } - }; - - ledgerDelegate_->getPageCount(cbPageCount); -} - -void TransactionsViewModel::ledgerToTxData(const std::map> &rawData - , bool onNewBlock) -{ - int pageCnt = 0; - - signalOnEndLoading_ = true; - for (const auto &le : rawData) { - updateTransactionsPage(le.second); - emit updateProgress(int(rawData.size()) + pageCnt++); - } - initialLoadCompleted_ = true; -} - void TransactionsViewModel::onNewItems(const std::vector &newItems) { const int curLastIdx = rootNode_->nbChildren(); @@ -1031,7 +495,7 @@ void TransactionsViewModel::onDelRows(std::vector rows) { // optimize for contiguous ranges, if needed std::sort(rows.begin(), rows.end()); int rowCnt = rowCount(); - QMutexLocker locker(&updateMutex_); + //QMutexLocker locker(&updateMutex_); for (int i = 0; i < rows.size(); ++i) { const int row = rows[i] - i; // special hack for correcting row index after previous row deletion if ((row < 0) || row >= rowCnt) { @@ -1040,6 +504,19 @@ void TransactionsViewModel::onDelRows(std::vector rows) beginRemoveRows(QModelIndex(), row, row); rootNode_->del(row); + auto itIndex = itemIndex_.begin(); + while (itIndex != itemIndex_.end()) { + if (itIndex->second < row) { + itIndex++; + continue; + } + if (itIndex->second == row) { + itIndex = itemIndex_.erase(itIndex); + continue; + } + itIndex->second--; + itIndex++; + } endRemoveRows(); rowCnt--; } @@ -1054,182 +531,197 @@ TransactionPtr TransactionsViewModel::getItem(const QModelIndex &index) const return node->item(); } -void TransactionsViewModel::updateTransactionDetails(const TransactionPtr &item - , const std::function &cb) +void TransactionsViewModel::onNewBlock(unsigned int curBlock) { - const auto &cbInited = [cb](const TransactionPtr &item) { - if (cb) { - cb(item); + if (curBlock_ == curBlock) { + return; + } + const unsigned int diff = curBlock - curBlock_; + curBlock_ = curBlock; + + for (const auto &node : rootNode_->children()) { + if (node->item()->confirmations == 0) { + continue; } - }; - TransactionsViewItem::initialize(item, armory_, walletsManager_, cbInited); + node->item()->confirmations += diff; + node->item()->txEntry.nbConf += diff; + } + emit dataChanged(index(0, static_cast(Columns::Status)) + , index(rootNode_->nbChildren() - 1, static_cast(Columns::Status))); } - -void TransactionsViewItem::initialize(const TransactionPtr &item, ArmoryConnection *armory - , const std::shared_ptr &walletsMgr - , std::function userCB) +void TransactionsViewModel::onLedgerEntries(const std::string &, uint32_t + , uint32_t, uint32_t curBlock, const std::vector &entries) { - const auto cbCheckIfInitializationCompleted = [item, userCB] { - if (item->initialized) { - return; - } - if (!item->dirStr.isEmpty() && !item->mainAddress.isEmpty() && !item->amountStr.isEmpty()) { - item->initialized = true; - userCB(item); - } - }; - - const auto cbMainAddr = [item, cbCheckIfInitializationCompleted](QString mainAddr, int addrCount) { - item->mainAddress = mainAddr; - item->addressCount = addrCount; - cbCheckIfInitializationCompleted(); - }; + if (!curBlock_) { + curBlock_ = curBlock; + } + else if (curBlock_ != curBlock) { + curBlock_ = curBlock; + onNewBlock(curBlock); + } + if (entries.empty()) { + return; + } - const auto cbInit = [item, walletsMgr, cbMainAddr, cbCheckIfInitializationCompleted, userCB] { - if (item->amountStr.isEmpty() && item->txHashesReceived) { - item->calcAmount(walletsMgr); + std::vector newNodes; + for (const auto &entry : entries) { + if (entry.txHash.empty()) { // invalid entry + continue; } - if (item->mainAddress.isEmpty()) { - if (!walletsMgr->getTransactionMainAddress(item->tx, item->walletID.toStdString(), (item->amount > 0), cbMainAddr)) { - userCB(nullptr); - } + const auto &item = createTxItem(entry); + const auto &itItem = itemIndex_.find({entry.txHash, item->walletID.toStdString()}); + if (itItem == itemIndex_.end()) { + newNodes.push_back(new TXNode(item)); } else { - cbCheckIfInitializationCompleted(); + const int row = itItem->second; + rootNode_->children()[row]->setItem(item); + emit dataChanged(index(row, static_cast(Columns::first)) + , index(row, static_cast(Columns::last))); } - }; + } - const auto cbTXs = [item, cbInit, userCB] - (const AsyncClient::TxBatchResult &txs, std::exception_ptr exPtr) - { - if (exPtr != nullptr) { - userCB(nullptr); - return; + if (!newNodes.empty()) { + beginInsertRows(QModelIndex(), rootNode_->nbChildren() + , rootNode_->nbChildren() + newNodes.size() - 1); + for (const auto &node : newNodes) { + itemIndex_[{node->item()->txEntry.txHash, node->item()->walletID.toStdString()}] = rootNode_->nbChildren(); + rootNode_->add(node); } - item->txIns.insert(txs.cbegin(), txs.cend()); - item->txHashesReceived = true; - cbInit(); - }; - const auto &cbDir = [item, cbInit](bs::sync::Transaction::Direction dir, std::vector inAddrs) { - item->direction = dir; - item->dirStr = QObject::tr(bs::sync::Transaction::toStringDir(dir)); - if (dir == bs::sync::Transaction::Direction::Received) { - if (inAddrs.size() == 1) { // likely a settlement address - switch (inAddrs[0].getType()) { - case AddressEntryType_P2WSH: - case AddressEntryType_P2SH: - case AddressEntryType_Multisig: - item->parentId = inAddrs[0]; - break; - default: break; - } - } - } - else if (dir == bs::sync::Transaction::Direction::Sent) { - for (int i = 0; i < item->tx.getNumTxOut(); ++i) { - TxOut out = item->tx.getTxOutCopy((int)i); - auto addr = bs::Address::fromHash(out.getScrAddressStr()); - switch (addr.getType()) { - case AddressEntryType_P2WSH: // likely a settlement address - case AddressEntryType_P2SH: - case AddressEntryType_Multisig: - item->parentId = addr; - break; - default: break; - } - if (!item->parentId.empty()) { - break; - } - } - } - else if (dir == bs::sync::Transaction::Direction::PayIn) { - for (int i = 0; i < item->tx.getNumTxOut(); ++i) { - TxOut out = item->tx.getTxOutCopy((int)i); - auto addr = bs::Address::fromHash(out.getScrAddressStr()); - switch (addr.getType()) { - case AddressEntryType_P2WSH: - case AddressEntryType_P2SH: - case AddressEntryType_Multisig: - item->groupId = addr; - break; - default: break; - } - if (!item->groupId.empty()) { - break; - } - } - } - else if (dir == bs::sync::Transaction::Direction::PayOut) { - if (inAddrs.size() == 1) { - item->groupId = inAddrs[0]; - } - } - cbInit(); - }; + endInsertRows(); + } - const auto cbTX = [item, armory, walletsMgr, cbTXs, cbInit, cbDir, cbMainAddr, userCB](const Tx &newTx) { - if (!newTx.isInitialized()) { - userCB(nullptr); - return; - } - if (item->comment.isEmpty()) { - item->comment = item->wallets.empty() ? QString() - : QString::fromStdString(item->wallets[0]->getTransactionComment(item->txEntry.txHash)); - const auto endLineIndex = item->comment.indexOf(QLatin1Char('\n')); - if (endLineIndex != -1) { - item->comment = item->comment.left(endLineIndex) + QLatin1String("..."); - } - } + std::vector txWallet; + txWallet.reserve(entries.size()); + for (const auto &entry : entries) { + const auto &walletId = entry.walletIds.empty() ? std::string{} : *(entry.walletIds.cbegin()); + txWallet.push_back({ entry.txHash, walletId, entry.value }); + } + emit needTXDetails(txWallet, true, {}); +} - if (!item->tx.isInitialized()) { - item->tx = std::move(newTx); - std::set txHashSet; - for (size_t i = 0; i < item->tx.getNumTxIn(); i++) { - TxIn in = item->tx.getTxInCopy(i); - OutPoint op = in.getOutPoint(); - if (item->txIns.find(op.getTxHash()) == item->txIns.end()) { - txHashSet.insert(op.getTxHash()); +void TransactionsViewModel::onZCsInvalidated(const std::vector& txHashes) +{ + for (const auto& txHash : txHashes) { + for (const auto& node : rootNode_->nodesByTxHash(txHash)) { + const int row = node->row(); + beginRemoveRows(QModelIndex(), row, row); + auto removedNode = rootNode_->take(row); + itemIndex_.erase({ node->item()->txEntry.txHash, node->item()->walletID.toStdString() }); + for (auto& idx : itemIndex_) { + if (idx.second > row) { + idx.second--; } } - if (txHashSet.empty()) { - item->txHashesReceived = true; - } - else { - if (!armory->getTXsByHash(txHashSet, cbTXs, true)) { - userCB(nullptr); - } + endRemoveRows(); + if (removedNode) { + removedNode->item()->curBlock = curBlock_; + invalidatedNodes_[{ txHash, node->item()->walletID.toStdString() }] = removedNode; } } - else { - item->txHashesReceived = true; - } + } + std::vector txWallet; + txWallet.reserve(invalidatedNodes_.size()); + for (const auto& invNode : invalidatedNodes_) { + const auto& entry = invNode.second->item()->txEntry; + const auto& walletId = entry.walletIds.empty() ? std::string{} : *(entry.walletIds.cbegin()); + txWallet.push_back({ entry.txHash, walletId, entry.value }); + } + emit needTXDetails(txWallet, false, {}); +} - if (item->dirStr.isEmpty()) { - if (!walletsMgr->getTransactionDirection(item->tx, item->walletID.toStdString(), cbDir)) { - userCB(nullptr); +void TransactionsViewModel::onTXDetails(const std::vector &txDet) +{ + std::vector newNodes; + for (const auto &tx : txDet) { + TransactionPtr item; + int row = -1; + const auto &itIndex = itemIndex_.find({tx.txHash, tx.walletId}); + if (itIndex == itemIndex_.end()) { + const auto& itInv = invalidatedNodes_.find({ tx.txHash, tx.walletId }); + if (itInv == invalidatedNodes_.end()) { + continue; } + const auto& invNode = itInv->second; + newNodes.push_back(invNode); + item = invNode->item(); + invalidatedNodes_.erase(itInv); } else { - if (item->txHashesReceived) { - cbInit(); + row = itIndex->second; + if (row >= rootNode_->nbChildren()) { + logger_->warn("[{}] invalid row: {} of {}", __func__, row, rootNode_->nbChildren()); + continue; } + item = rootNode_->children()[row]->item(); } - }; + item->walletName = QString::fromStdString(tx.walletName); + item->direction = tx.direction; + item->dirStr = QObject::tr(bs::sync::Transaction::toStringDir(tx.direction)); + item->isValid = tx.isValid ? bs::sync::TxValidity::Valid : bs::sync::TxValidity::Invalid; + if (!tx.comment.empty()) { + item->comment = QString::fromStdString(tx.comment); + } + item->amountStr = QString::fromStdString(tx.amount); - if (item->initialized) { - userCB(item); - } else { - if (item->tx.isInitialized()) { - cbTX(item->tx); + item->addressCount = tx.outAddresses.size(); + switch (tx.outAddresses.size()) { + case 0: + item->mainAddress = tr("no addresses"); + break; + case 1: + item->mainAddress = QString::fromStdString(tx.outAddresses[0].display()); + break; + default: + item->mainAddress = tr("%1 output addresses").arg(tx.outAddresses.size()); + break; + } + item->tx = tx.tx; + item->inputAddresses = tx.inputAddresses; + item->outputAddresses = tx.outputAddresses; + item->changeAddress = tx.changeAddress; + if (row >= 0) { + emit dataChanged(index(row, static_cast(Columns::Wallet)) + , index(row, static_cast(Columns::Comment))); } else { - if (!armory->getTxByHash(item->txEntry.txHash, cbTX, true)) { - userCB(nullptr); + item->confirmations = curBlock_ - item->curBlock + 1; + //FIXME: this code doesn't provide proper conf #, even with caching turned off: + // curBlock_ + 1 - tx.tx.getTxHeight(); + item->txEntry.nbConf = item->confirmations; + } + } + if (!newNodes.empty()) { + const int startRow = rootNode_->nbChildren(); + const int endRow = rootNode_->nbChildren() + newNodes.size() - 1; + beginInsertRows(QModelIndex(), startRow, endRow); + for (const auto& node : newNodes) { + itemIndex_[{node->item()->txEntry.txHash, node->item()->walletID.toStdString()}] = rootNode_->nbChildren(); + rootNode_->add(node); + } + endInsertRows(); + } +} + +size_t TransactionsViewModel::removeEntriesFor(const bs::sync::HDWalletData& wallet) +{ + std::vector delRows; + for (const auto& group : wallet.groups) { + for (const auto& leaf : group.leaves) { + for (const auto& id : leaf.ids) { + for (const auto& index : itemIndex_) { + if (index.first.walletId == id) { + delRows.push_back(index.second); + } + } } } } + onDelRows(delRows); + return delRows.size(); } + static bool isSpecialWallet(const std::shared_ptr &wallet) { if (!wallet) { diff --git a/BlockSettleUILib/TransactionsViewModel.h b/BlockSettleUILib/TransactionsViewModel.h index 503965f99..f66949689 100644 --- a/BlockSettleUILib/TransactionsViewModel.h +++ b/BlockSettleUILib/TransactionsViewModel.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -20,9 +20,8 @@ #include #include #include -#include "ArmoryConnection.h" #include "AsyncClient.h" -#include "Wallets/SyncWallet.h" +#include "Wallets/SignerDefs.h" namespace spdlog { class logger; @@ -42,11 +41,16 @@ struct TransactionsViewItem { bs::TXEntry txEntry; Tx tx; + std::vector prevTXs; bool initialized = false; QString mainAddress; int addressCount; + std::vector outAddresses; bs::sync::Transaction::Direction direction = bs::sync::Transaction::Unknown; std::vector> wallets; + std::vector inputAddresses; + std::vector outputAddresses; + bs::sync::AddressDetails changeAddress; QString dirStr; QString walletName; QString walletID; @@ -56,7 +60,7 @@ struct TransactionsViewItem BTCNumericTypes::balance_type amount = 0; int32_t txOutIndex{-1}; bool txMultipleOutIndex{false}; - bs::sync::TxValidity isValid = bs::sync::TxValidity::Invalid; + bs::sync::TxValidity isValid = bs::sync::TxValidity::Unknown; bool isCPFP = false; int confirmations = 0; @@ -64,9 +68,6 @@ struct TransactionsViewItem BinaryData groupId; bool isSet() const { return (!txEntry.txHash.empty() && !walletID.isEmpty()); } - static void initialize(const TransactionPtr &item, ArmoryConnection * - , const std::shared_ptr & - , std::function); void calcAmount(const std::shared_ptr &); bool containsInputsFrom(const Tx &tx) const; @@ -75,6 +76,7 @@ struct TransactionsViewItem bool isPayin() const; bs::Address filterAddress; + uint32_t curBlock{ 0 }; private: bool txHashesReceived{ false }; @@ -90,18 +92,21 @@ class TXNode ~TXNode() { clear(); } std::shared_ptr item() const { return item_; } + void setItem(const std::shared_ptr &item) { item_ = item; } size_t nbChildren() const { return children_.size(); } bool hasChildren() const { return !children_.empty(); } TXNode *child(int index) const; const QList &children() const { return children_; } TXNode *parent() const { return parent_; } TXNode *find(const bs::TXEntry &) const; + TXNode* find(const BinaryData &txHash) const; std::vector nodesByTxHash(const BinaryData &) const; void clear(bool del = true); void setData(const TransactionsViewItem &data) { *item_ = data; } void add(TXNode *child); void del(int index); + TXNode *take(int index); int row() const { return row_; } unsigned int level() const; QVariant data(int, int) const; @@ -124,21 +129,11 @@ Q_DECLARE_METATYPE(TransactionsViewItem) Q_DECLARE_METATYPE(TransactionItems) -class TransactionsViewModel : public QAbstractItemModel, public ArmoryCallbackTarget +class TransactionsViewModel : public QAbstractItemModel { Q_OBJECT public: - TransactionsViewModel(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &defWlt - , const bs::Address &filterAddress = bs::Address() - , QObject* parent = nullptr); - TransactionsViewModel(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , QObject* parent = nullptr); + TransactionsViewModel(const std::shared_ptr &, QObject* parent = nullptr); ~TransactionsViewModel() noexcept override; TransactionsViewModel(const TransactionsViewModel&) = delete; @@ -146,10 +141,8 @@ Q_OBJECT TransactionsViewModel(TransactionsViewModel&&) = delete; TransactionsViewModel& operator = (TransactionsViewModel&&) = delete; - void loadAllWallets(bool onNewBlock=false); size_t itemsCount() const { return rootNode_->nbChildren(); } -public: int columnCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; @@ -163,33 +156,25 @@ Q_OBJECT TransactionPtr getOldestItem() const { return oldestItem_; } TXNode *getNode(const QModelIndex &) const; - bool isTxRevocable(const Tx& tx); + void onLedgerEntries(const std::string &filter, uint32_t totalPages + , uint32_t curPage, uint32_t curBlock, const std::vector &); + void onZCsInvalidated(const std::vector& txHashes); + void onNewBlock(unsigned int topBlock); + void onTXDetails(const std::vector &); + size_t removeEntriesFor(const bs::sync::HDWalletData&); + +signals: + void needTXDetails(const std::vector &, bool useCache, const bs::Address &); private slots: - void updatePage(); - void refresh(); - void onWalletDeleted(std::string walletId); void onNewItems(const std::vector &); void onDelRows(std::vector rows); void onItemConfirmed(const TransactionPtr); void onRefreshTxValidity(); private: - void onNewBlock(unsigned int height, unsigned int branchHgt) override; - void onStateChanged(ArmoryState) override; - void onZCReceived(const std::string& requestId, const std::vector &) override; - void onZCInvalidated(const std::set &ids) override; - - void init(); void clear(); - void loadLedgerEntries(bool onNewBlock=false); - void ledgerToTxData(const std::map> &rawData - , bool onNewBlock=false); - std::pair updateTransactionsPage(const std::vector &); - void updateBlockHeight(const std::vector> &); - void updateTransactionDetails(const TransactionPtr &item - , const std::function &cb); - std::shared_ptr itemFromTransaction(const bs::TXEntry &); + std::shared_ptr createTxItem(const bs::TXEntry &); signals: void dataLoaded(int count); @@ -220,18 +205,32 @@ private slots: }; private: - std::unique_ptr rootNode_; + std::unique_ptr rootNode_; TransactionPtr oldestItem_; std::shared_ptr logger_; - std::shared_ptr ledgerDelegate_; - std::shared_ptr walletsManager_; mutable QMutex updateMutex_; std::shared_ptr defaultWallet_; std::atomic_bool signalOnEndLoading_{ false }; - const bool allWallets_; std::shared_ptr stopped_; std::atomic_bool initialLoadCompleted_{ true }; + struct ItemKey { + BinaryData txHash; + std::string walletId; + + bool operator<(const ItemKey &other) const + { + return ((txHash < other.txHash) || ((txHash == other.txHash) + && (walletId < other.walletId))); + } + bool operator==(const ItemKey &other) const + { + return ((txHash == other.txHash) && (walletId == other.walletId)); + } + }; + std::map itemIndex_; + std::map invalidatedNodes_; + // If set, amount field will show only related address balance changes // (without fees because fees are related to transaction, not address). // Right now used with AddressDetailDialog only. @@ -240,6 +239,8 @@ private slots: // Tx that could be revoked std::map revocableTxs_; + + uint32_t curBlock_{ 0 }; }; #endif // __TRANSACTIONS_VIEW_MODEL_H__ diff --git a/BlockSettleUILib/TransactionsWidget.cpp b/BlockSettleUILib/TransactionsWidget.cpp index 136be45ba..cf9143a2b 100644 --- a/BlockSettleUILib/TransactionsWidget.cpp +++ b/BlockSettleUILib/TransactionsWidget.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -20,7 +20,7 @@ #include "BSMessageBox.h" #include "CreateTransactionDialogAdvanced.h" #include "PasswordDialogDataWrapper.h" -#include "TradesUtils.h" +#include "Wallets/TradesUtils.h" #include "TransactionsViewModel.h" #include "TransactionDetailDialog.h" #include "Wallets/SyncHDWallet.h" @@ -28,7 +28,7 @@ #include "UiUtils.h" #include "UtxoReservationManager.h" -static const QString c_allWalletsId = QLatin1String("all"); +static const QString kAllWalletsId = QLatin1String("all"); using namespace bs::sync; @@ -161,12 +161,12 @@ class TransactionsSortFilterModel : public QSortFilterProxyModel this->walletIds = walletIds; this->searchString = searchString; this->transactionDirection = direction; - - appSettings_->set(ApplicationSettings::TransactionFilter, - QVariantList() << (this->walletIds.isEmpty() ? - QStringList() << c_allWalletsId : this->walletIds) << - static_cast(direction)); - + if (appSettings_) { + appSettings_->set(ApplicationSettings::TransactionFilter, + QVariantList() << (this->walletIds.isEmpty() ? + QStringList() << kAllWalletsId : this->walletIds) << + static_cast(direction)); + } invalidateFilter(); } @@ -225,7 +225,7 @@ TransactionsWidget::TransactionsWidget(QWidget* parent) if (txNode->item()->isPayin()) { contextMenu_.addAction(actionRevoke_); actionRevoke_->setData(sourceIndex); - actionRevoke_->setEnabled(model_->isTxRevocable(txNode->item()->tx)); +// actionRevoke_->setEnabled(model_->isTxRevocable(txNode->item()->tx)); } else { actionRevoke_->setData(-1); @@ -277,22 +277,44 @@ TransactionsWidget::TransactionsWidget(QWidget* parent) TransactionsWidget::~TransactionsWidget() = default; -void TransactionsWidget::init(const std::shared_ptr &walletsMgr - , const std::shared_ptr &armory - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr &signContainer - , const std::shared_ptr &appSettings - , const std::shared_ptr &logger) - +void TransactionsWidget::init(const std::shared_ptr &logger + , const std::shared_ptr &model) { - TransactionsWidgetInterface::init(walletsMgr, armory, utxoReservationManager, signContainer, appSettings, logger); + TransactionsWidgetInterface::init(logger); + scheduleDateFilterCheck(); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletChanged, this, &TransactionsWidget::walletsChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletDeleted, [this](std::string) { walletsChanged(); }); + model_ = model; + connect(model_.get(), &TransactionsViewModel::dataLoaded, this, &TransactionsWidget::onDataLoaded, Qt::QueuedConnection); + connect(model_.get(), &TransactionsViewModel::initProgress, this, &TransactionsWidget::onProgressInited); + connect(model_.get(), &TransactionsViewModel::updateProgress, this, &TransactionsWidget::onProgressUpdated); - scheduleDateFilterCheck(); + sortFilterModel_ = new TransactionsSortFilterModel(appSettings_, this); + sortFilterModel_->setSourceModel(model.get()); + sortFilterModel_->setDynamicSortFilter(true); + + connect(sortFilterModel_, &TransactionsSortFilterModel::rowsInserted, this, &TransactionsWidget::updateResultCount); + connect(sortFilterModel_, &TransactionsSortFilterModel::rowsRemoved, this, &TransactionsWidget::updateResultCount); + connect(sortFilterModel_, &TransactionsSortFilterModel::modelReset, this, &TransactionsWidget::updateResultCount); + + auto updateDateTimes = [this]() { + sortFilterModel_->updateDates(ui_->dateEditStart->date(), ui_->dateEditEnd->date()); + }; + connect(ui_->dateEditStart, &QDateTimeEdit::dateTimeChanged, updateDateTimes); + connect(ui_->dateEditEnd, &QDateTimeEdit::dateTimeChanged, updateDateTimes); + + connect(ui_->searchField, &QLineEdit::textChanged, [=](const QString& text) { + sortFilterModel_->updateFilters(sortFilterModel_->walletIds, text, sortFilterModel_->transactionDirection); + }); + + ui_->treeViewTransactions->setSortingEnabled(true); + ui_->treeViewTransactions->setModel(sortFilterModel_); + ui_->treeViewTransactions->hideColumn(static_cast(TransactionsViewModel::Columns::TxHash)); + + ui_->treeViewTransactions->sortByColumn(static_cast(TransactionsViewModel::Columns::Date), Qt::DescendingOrder); + ui_->treeViewTransactions->sortByColumn(static_cast(TransactionsViewModel::Columns::Status), Qt::AscendingOrder); } +#if 0 void TransactionsWidget::SetTransactionsModel(const std::shared_ptr& model) { model_ = model; @@ -330,6 +352,26 @@ void TransactionsWidget::SetTransactionsModel(const std::shared_ptrtreeViewTransactions->hideColumn(static_cast(TransactionsViewModel::Columns::MissedBlocks)); } +#endif + +void TransactionsWidget::onHDWalletDetails(const bs::sync::HDWalletData& wallet) +{ + wallets_[wallet.id] = wallet; + walletsChanged(); +} + +void TransactionsWidget::onWalletDeleted(const bs::sync::WalletInfo& wi) +{ + const auto& itWallet = wallets_.find(*wi.ids.cbegin()); + if (itWallet != wallets_.end()) { + const auto nbRemoved = model_->removeEntriesFor(itWallet->second); + logger_->debug("[{}] removed {} entries from wallet {}", __func__, nbRemoved + , itWallet->second.name); + wallets_.erase(itWallet); + } + walletsChanged(); +} + void TransactionsWidget::onDataLoaded(int count) { @@ -398,41 +440,32 @@ static inline bool exactlyThisLeaf(const QStringList &ids, const QStringList &wa void TransactionsWidget::walletsChanged() { - QStringList walletIds; - int direction; - - const auto varList = appSettings_->get(ApplicationSettings::TransactionFilter).toList(); - walletIds = varList.first().toStringList(); - direction = varList.last().toInt(); - int currentIndex = -1; - int primaryWalletIndex = 0; ui_->walletBox->clear(); ui_->walletBox->addItem(tr("All Wallets")); int index = 1; - for (const auto &hdWallet : walletsManager_->hdWallets()) { - ui_->walletBox->addItem(QString::fromStdString(hdWallet->name())); - QStringList allLeafIds = walletLeavesIds(hdWallet); - - if (exactlyThisLeaf(walletIds, allLeafIds)) { - currentIndex = index; - } - - if (hdWallet == walletsManager_->getPrimaryWallet()) { - primaryWalletIndex = index; + for (const auto& hdWallet : wallets_) { + ui_->walletBox->addItem(QString::fromStdString(hdWallet.second.name)); + QStringList allLeafIds; + for (const auto& group : hdWallet.second.groups) { + for (const auto& leaf : group.leaves) { + for (const auto& id : leaf.ids) { + allLeafIds.append(QString::fromStdString(id)); + } + } } - ui_->walletBox->setItemData(index++, allLeafIds, UiUtils::WalletIdRole); - for (const auto &group : hdWallet->getGroups()) { +#if 0 + for (const auto& group : hdWallet->groups) { if (group->type() == bs::core::wallet::Type::Settlement) { continue; } ui_->walletBox->addItem(QString::fromStdString(" " + group->name())); const auto groupIndex = index++; QStringList groupLeafIds; - for (const auto &leaf : group->getLeaves()) { + for (const auto& leaf : group->getLeaves()) { groupLeafIds << QString::fromStdString(leaf->walletId()); ui_->walletBox->addItem(QString::fromStdString(" " + leaf->shortName())); @@ -456,21 +489,13 @@ void TransactionsWidget::walletsChanged() } ui_->typeFilterComboBox->setCurrentIndex(direction); - +#else //0 + } +#endif //0 if (currentIndex >= 0) { ui_->walletBox->setCurrentIndex(currentIndex); } else { - if (walletIds.contains(c_allWalletsId)) { - ui_->walletBox->setCurrentIndex(0); - } else { - const auto primaryWallet = walletsManager_->getPrimaryWallet(); - - if (primaryWallet) { - ui_->walletBox->setCurrentIndex(primaryWalletIndex); - } else { - ui_->walletBox->setCurrentIndex(0); - } - } + ui_->walletBox->setCurrentIndex(0); } } @@ -496,8 +521,11 @@ void TransactionsWidget::showTransactionDetails(const QModelIndex& index) return; } - TransactionDetailDialog transactionDetailDialog(txItem, walletsManager_, armory_, this); - transactionDetailDialog.exec(); + auto txDetailDialog = new TransactionDetailDialog(txItem, this); + connect(txDetailDialog, &QDialog::finished, [txDetailDialog](int) { + txDetailDialog->deleteLater(); + }); + txDetailDialog->show(); } void TransactionsWidget::updateResultCount() diff --git a/BlockSettleUILib/TransactionsWidget.h b/BlockSettleUILib/TransactionsWidget.h index d9bba4508..a3a98203d 100644 --- a/BlockSettleUILib/TransactionsWidget.h +++ b/BlockSettleUILib/TransactionsWidget.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -18,6 +18,7 @@ #include "BinaryData.h" #include "BSErrorCode.h" #include "TransactionsWidgetInterface.h" +#include "Wallets/SignerDefs.h" namespace spdlog { class logger; @@ -33,10 +34,10 @@ namespace bs { } class ApplicationSettings; class ArmoryConnection; +class HeadlessContainer; class TransactionsProxy; class TransactionsViewModel; class TransactionsSortFilterModel; -class WalletSignerContainer; class TransactionsWidget : public TransactionsWidgetInterface @@ -47,16 +48,14 @@ Q_OBJECT TransactionsWidget(QWidget* parent = nullptr ); ~TransactionsWidget() override; - void init(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr& - , const std::shared_ptr &); - void SetTransactionsModel(const std::shared_ptr &); + void init(const std::shared_ptr & + , const std::shared_ptr &); void shortcutActivated(ShortcutType s) override; + void onHDWalletDetails(const bs::sync::HDWalletData&); + void onWalletDeleted(const bs::sync::WalletInfo&); + private slots: void showTransactionDetails(const QModelIndex& index); void updateResultCount(); @@ -70,9 +69,9 @@ private slots: private: void scheduleDateFilterCheck(); std::unique_ptr ui_; + std::unordered_map wallets_; TransactionsSortFilterModel * sortFilterModel_; - }; diff --git a/BlockSettleUILib/TransactionsWidgetInterface.cpp b/BlockSettleUILib/TransactionsWidgetInterface.cpp index 9b13b99d2..296805e5b 100644 --- a/BlockSettleUILib/TransactionsWidgetInterface.cpp +++ b/BlockSettleUILib/TransactionsWidgetInterface.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -13,8 +13,9 @@ #include "ApplicationSettings.h" #include "BSMessageBox.h" #include "CreateTransactionDialogAdvanced.h" +#include "Wallets/HeadlessContainer.h" #include "PasswordDialogDataWrapper.h" -#include "TradesUtils.h" +#include "Wallets/TradesUtils.h" #include "TransactionsViewModel.h" #include "TransactionDetailDialog.h" #include "Wallets/SyncHDWallet.h" @@ -39,9 +40,6 @@ TransactionsWidgetInterface::TransactionsWidgetInterface(QWidget *parent) qApp->clipboard()->setText(curTx_); }); - actionRevoke_ = new QAction(tr("Revoke"), this); - connect(actionRevoke_, &QAction::triggered, this, &TransactionsWidgetInterface::onRevokeSettlement); - actionRBF_ = new QAction(tr("Replace-By-Fee (RBF)"), this); connect(actionRBF_, &QAction::triggered, this, &TransactionsWidgetInterface::onCreateRBFDialog); @@ -49,151 +47,9 @@ TransactionsWidgetInterface::TransactionsWidgetInterface(QWidget *parent) connect(actionCPFP_, &QAction::triggered, this, &TransactionsWidgetInterface::onCreateCPFPDialog); } -void TransactionsWidgetInterface::init(const std::shared_ptr &walletsMgr - , const std::shared_ptr &armory - , const std::shared_ptr &utxoReservationManager - , const std::shared_ptr &signContainer - , const std::shared_ptr &appSettings - , const std::shared_ptr &logger) - +void TransactionsWidgetInterface::init(const std::shared_ptr &logger) { - walletsManager_ = walletsMgr; - armory_ = armory; - utxoReservationManager_ = utxoReservationManager; - signContainer_ = signContainer; - appSettings_ = appSettings; logger_ = logger; - - connect(signContainer_.get(), &SignContainer::TXSigned, this, &TransactionsWidgetInterface::onTXSigned); -} - -void TransactionsWidgetInterface::onRevokeSettlement() -{ - auto txItem = model_->getItem(actionRevoke_->data().toModelIndex()); - if (!txItem) { - SPDLOG_LOGGER_ERROR(logger_, "item not found"); - return; - } - auto args = std::make_shared(); - - auto payoutCb = bs::tradeutils::PayoutResultCb([this, args, txItem] - (bs::tradeutils::PayoutResult result) - { - const auto ×tamp = QDateTime::currentDateTimeUtc(); - QMetaObject::invokeMethod(qApp, [this, args, txItem, timestamp, result] { - if (!result.success) { - SPDLOG_LOGGER_ERROR(logger_, "creating payout failed: {}", result.errorMsg); - BSMessageBox(BSMessageBox::critical, tr("Revoke Transaction") - , tr("Revoke failed") - , tr("failed to create pay-out TX"), this).exec(); - return; - } - - constexpr int kRevokeTimeout = 60; - const auto &settlementIdHex = args->settlementId.toHexStr(); - bs::sync::PasswordDialogData dlgData; - dlgData.setValue(bs::sync::PasswordDialogData::SettlementId, QString::fromStdString(settlementIdHex)); - dlgData.setValue(bs::sync::PasswordDialogData::Title, tr("Settlement Revoke")); - dlgData.setValue(bs::sync::PasswordDialogData::DurationLeft, kRevokeTimeout * 1000); - dlgData.setValue(bs::sync::PasswordDialogData::DurationTotal, kRevokeTimeout * 1000); - dlgData.setValue(bs::sync::PasswordDialogData::SettlementPayOutVisible, true); - - // Set timestamp that will be used by auth eid server to update timers. - dlgData.setValue(bs::sync::PasswordDialogData::DurationTimestamp, static_cast(timestamp.toSecsSinceEpoch())); - - dlgData.setValue(bs::sync::PasswordDialogData::ProductGroup, tr(bs::network::Asset::toString(bs::network::Asset::SpotXBT))); - dlgData.setValue(bs::sync::PasswordDialogData::Security, txItem->comment); - dlgData.setValue(bs::sync::PasswordDialogData::Product, "XXX"); - dlgData.setValue(bs::sync::PasswordDialogData::Side, tr("Revoke")); - dlgData.setValue(bs::sync::PasswordDialogData::Price, tr("N/A")); - - dlgData.setValue(bs::sync::PasswordDialogData::Market, "XBT"); - dlgData.setValue(bs::sync::PasswordDialogData::SettlementId, settlementIdHex); - dlgData.setValue(bs::sync::PasswordDialogData::RequesterAuthAddressVerified, true); - dlgData.setValue(bs::sync::PasswordDialogData::ResponderAuthAddressVerified, true); - dlgData.setValue(bs::sync::PasswordDialogData::SigningAllowed, true); - - dlgData.setValue(bs::sync::PasswordDialogData::ExpandTxInfo, - appSettings_->get(ApplicationSettings::AdvancedTxDialogByDefault).toBool()); - - const auto amount = args->amount.GetValueBitcoin(); - SPDLOG_LOGGER_DEBUG(logger_, "revoke fee={}, qty={} ({}), recv addr: {}" - ", settl addr: {}", result.signRequest.fee, amount - , amount * BTCNumericTypes::BalanceDivider, args->recvAddr.display() - , result.settlementAddr.display()); - - //note: signRequest should be a shared_ptr - auto signObj = result.signRequest; - const auto reqId = signContainer_->signSettlementPayoutTXRequest(signObj - , { args->settlementId, args->cpAuthPubKey, false }, dlgData); - if (reqId) { - revokeIds_.insert(reqId); - } - else { - BSMessageBox(BSMessageBox::critical, tr("Revoke Transaction") - , tr("Revoke failed") - , tr("failed to send TX request to signer"), this).exec(); - } - }); - }); - - const auto &cbSettlAuth = [this, args, payoutCb](const bs::Address &ownAuthAddr) - { - if (ownAuthAddr.empty()) { - QMetaObject::invokeMethod(this, [this] { - BSMessageBox(BSMessageBox::critical, tr("Revoke Transaction") - , tr("Failed to create revoke transaction") - , tr("auth wallet doesn't contain settlement metadata"), this).exec(); - }); - return; - } - args->ourAuthAddress = ownAuthAddr; - bs::tradeutils::createPayout(*args, payoutCb, false); - }; - const auto &cbSettlCP = [this, args, cbSettlAuth] - (const BinaryData &settlementId, const BinaryData &dealerAuthKey) - { - if (settlementId.empty() || dealerAuthKey.empty()) { - cbSettlAuth({}); - return; - } - args->settlementId = settlementId; - args->cpAuthPubKey = dealerAuthKey; - signContainer_->getSettlAuthAddr(walletsManager_->getPrimaryWallet()->walletId() - , settlementId, cbSettlAuth); - }; - const auto &cbDialog = [this, args, cbSettlCP] - (const TransactionPtr &txItem) - { - for (int i = 0; i < txItem->tx.getNumTxOut(); ++i) { - const auto &txOut = txItem->tx.getTxOutCopy(i); - const auto &addr = bs::Address::fromTxOut(txOut); - if (addr.getType() == AddressEntryType_P2WSH) { - args->amount = bs::XBTAmount{ txOut.getValue() }; - break; - } - } - - const auto &xbtWallet = walletsManager_->getDefaultWallet(); - args->walletsMgr = walletsManager_; - args->armory = armory_; - args->signContainer = signContainer_; - args->payinTxId = txItem->txEntry.txHash; - args->outputXbtWallet = xbtWallet; - - xbtWallet->getNewExtAddress([this, args, cbSettlCP](const bs::Address &addr) { - args->recvAddr = addr; - signContainer_->getSettlCP(walletsManager_->getPrimaryWallet()->walletId() - , args->payinTxId, cbSettlCP); - }); - }; - - if (txItem->initialized) { - cbDialog(txItem); - } - else { - TransactionsViewItem::initialize(txItem, armory_.get(), walletsManager_, cbDialog); - } } void TransactionsWidgetInterface::onCreateRBFDialog() @@ -206,10 +62,9 @@ void TransactionsWidgetInterface::onCreateRBFDialog() const auto &cbDialog = [this](const TransactionPtr &txItem) { try { - auto dlg = CreateTransactionDialogAdvanced::CreateForRBF(armory_ - , walletsManager_, utxoReservationManager_, signContainer_, logger_, appSettings_, txItem->tx - , this); - dlg->exec(); + //FIXME: auto dlg = CreateTransactionDialogAdvanced::CreateForRBF(topBlock_ + // , logger_, txItem->tx, this); + //dlg->exec(); } catch (const std::exception &e) { BSMessageBox(BSMessageBox::critical, tr("RBF Transaction"), tr("Failed to create RBF transaction") @@ -220,9 +75,6 @@ void TransactionsWidgetInterface::onCreateRBFDialog() if (txItem->initialized) { cbDialog(txItem); } - else { - TransactionsViewItem::initialize(txItem, armory_.get(), walletsManager_, cbDialog); - } } void TransactionsWidgetInterface::onCreateCPFPDialog() @@ -242,10 +94,9 @@ void TransactionsWidgetInterface::onCreateCPFPDialog() break; } } - auto dlg = CreateTransactionDialogAdvanced::CreateForCPFP(armory_ - , walletsManager_, utxoReservationManager_, signContainer_, wallet, logger_, appSettings_ - , txItem->tx, this); - dlg->exec(); + //FIXME: auto dlg = CreateTransactionDialogAdvanced::CreateForCPFP(topBlock_ + // , logger_, , txItem->tx, this); + //dlg->exec(); } catch (const std::exception &e) { BSMessageBox(BSMessageBox::critical, tr("CPFP Transaction"), tr("Failed to create CPFP transaction") @@ -256,9 +107,6 @@ void TransactionsWidgetInterface::onCreateCPFPDialog() if (txItem->initialized) { cbDialog(txItem); } - else { - TransactionsViewItem::initialize(txItem, armory_.get(), walletsManager_, cbDialog); - } } void TransactionsWidgetInterface::onTXSigned(unsigned int id, BinaryData signedTX diff --git a/BlockSettleUILib/TransactionsWidgetInterface.h b/BlockSettleUILib/TransactionsWidgetInterface.h index 18baa44b1..54b13c77a 100644 --- a/BlockSettleUILib/TransactionsWidgetInterface.h +++ b/BlockSettleUILib/TransactionsWidgetInterface.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -29,8 +29,8 @@ namespace bs { } class ApplicationSettings; class ArmoryConnection; +class HeadlessContainer; class TransactionsViewModel; -class WalletSignerContainer; class TransactionsWidgetInterface : public TabWithShortcut { Q_OBJECT @@ -38,15 +38,9 @@ class TransactionsWidgetInterface : public TabWithShortcut { explicit TransactionsWidgetInterface(QWidget *parent = nullptr); ~TransactionsWidgetInterface() noexcept override = default; - void init(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr& - , const std::shared_ptr &); + void init(const std::shared_ptr &); protected slots: - void onRevokeSettlement(); void onCreateRBFDialog(); void onCreateCPFPDialog(); void onTXSigned(unsigned int id, BinaryData signedTX, bs::error::ErrorCode, std::string error); @@ -54,7 +48,7 @@ protected slots: protected: std::shared_ptr logger_; std::shared_ptr walletsManager_; - std::shared_ptr signContainer_; + std::shared_ptr signContainer_; std::shared_ptr armory_; std::shared_ptr utxoReservationManager_; std::shared_ptr appSettings_; diff --git a/BlockSettleUILib/UiUtils.cpp b/BlockSettleUILib/UiUtils.cpp index cfe95a3a3..a2d8948ce 100644 --- a/BlockSettleUILib/UiUtils.cpp +++ b/BlockSettleUILib/UiUtils.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -11,14 +11,12 @@ #include "UiUtils.h" #include "ApplicationSettings.h" -#include "AuthAddressManager.h" #include "BinaryData.h" -#include "BlockDataManagerConfig.h" #include "BTCNumericTypes.h" #include "BtcUtils.h" #include "CoinControlModel.h" #include "CustomControls/QtAwesome.h" -#include "SignContainer.h" +#include "Wallets/SignContainer.h" #include "TxClasses.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" @@ -42,7 +40,8 @@ template bool contains(const std::vector& v, const T& value) return std::find(v.begin(), v.end(), value) != v.end(); } -const QLatin1String UiUtils::XbtCurrency = QLatin1String("XBT"); +static const std::string XbtCurrencyString = "XBT"; +const QString UiUtils::XbtCurrency = QString::fromStdString(XbtCurrencyString); void UiUtils::SetupLocale() { @@ -63,7 +62,7 @@ QString UiUtils::displayDateTime(const QDateTime& datetime) return datetime.toString(format); } -QString UiUtils::displayDateTime(uint64_t time) +QString UiUtils::displayDateTime(uint32_t time) // in UnixTime { return displayDateTime(QDateTime::fromTime_t(time)); } @@ -121,9 +120,17 @@ namespace UiUtils { return UnifyValueString(QLocale().toString(amountToBtc(value), 'f', GetAmountPrecisionXBT())); } + template <> QString displayAmount(int value) + { + if (value == INT_MAX) { + return CommonUiUtilsText::tr("Loading..."); + } + return UnifyValueString(QLocale().toString(amountToBtc(value), 'f', GetAmountPrecisionXBT())); + } + QString displayAmount(const bs::XBTAmount &amount) { - if (!amount.isValid()) { + if (amount.isZero()) { return CommonUiUtilsText::tr("Loading..."); } return UnifyValueString(QLocale().toString(amountToBtc(amount.GetValue()) @@ -280,18 +287,85 @@ int UiUtils::fillHDWalletsComboBox(QComboBox* comboBox, const std::shared_ptr &authAddressManager) +int UiUtils::fillHDWalletsComboBox(QComboBox* comboBox + , const std::vector& wallets, int walletTypes) +{ + int selected = 0; + const auto b = comboBox->blockSignals(true); + comboBox->clear(); + + auto addRow = [comboBox](const std::string& label, const std::string& walletId, WalletsTypes type) + { + if (WalletsTypes::None == type) { + return; + } + int i = comboBox->count(); + comboBox->addItem(QString::fromStdString(label)); + comboBox->setItemData(i, QString::fromStdString(walletId), UiUtils::WalletIdRole); + comboBox->setItemData(i, QVariant::fromValue(static_cast(type)), UiUtils::WalletType); + }; + + for (const auto& hdWallet : wallets) { + if (hdWallet.primary) { + selected = comboBox->count(); + } + + WalletsTypes type = WalletsTypes::None; + // HW wallets marked as offline too, make sure to check that first +/* if (!hdWallet->canMixLeaves()) { + + if (hdWallet->isHardwareOfflineWallet() && !(walletTypes & WalletsTypes::WatchOnly)) { + continue; + } + + for (auto const& leaf : hdWallet->getGroup(hdWallet->getXBTGroupType())->getLeaves()) { + std::string label = hdWallet->name(); + type = WalletsTypes::None; + + auto purpose = leaf->purpose(); + if (purpose == bs::hd::Purpose::Native && + (walletTypes & WalletsTypes::HardwareNativeSW)) { + label += " Native"; + type = WalletsTypes::HardwareNativeSW; + } else if (purpose == bs::hd::Purpose::Nested && + (walletTypes & WalletsTypes::HardwareNestedSW)) { + label += " Nested"; + type = WalletsTypes::HardwareNestedSW; + } else if (purpose == bs::hd::Purpose::NonSegWit && + (walletTypes & WalletsTypes::HardwareLegacy) && leaf->getTotalBalance() > 0) { + label += " Legacy"; + type = WalletsTypes::HardwareLegacy; + } + addRow(label, hdWallet->walletId(), type); + } + continue; + }*/ //TODO + + if (hdWallet.offline) { + type = (walletTypes & WalletsTypes::WatchOnly) ? WalletsTypes::WatchOnly : WalletsTypes::None; + } else if (walletTypes & WalletsTypes::Full) { + type = WalletsTypes::Full; + } + + addRow(hdWallet.name, hdWallet.id, type); + } + comboBox->blockSignals(b); + comboBox->setCurrentIndex(selected); + return selected; +} + +void UiUtils::fillAuthAddressesComboBoxWithSubmitted(QComboBox* comboBox + , const std::vector& addrs, int defaultIdx) { + const auto b = comboBox->blockSignals(true); comboBox->clear(); - const auto &addrList = authAddressManager->GetSubmittedAddressList(); - if (!addrList.empty()) { - const auto b = comboBox->blockSignals(true); - for (const auto &address : addrList) { + if (!addrs.empty()) { + for (const auto& address : addrs) { comboBox->addItem(QString::fromStdString(address.display())); } - comboBox->blockSignals(b); - QMetaObject::invokeMethod(comboBox, "setCurrentIndex", Q_ARG(int, authAddressManager->getDefaultIndex())); comboBox->setEnabled(true); + comboBox->blockSignals(b); + comboBox->setCurrentIndex(defaultIdx); } else { comboBox->setEnabled(false); } @@ -335,6 +409,20 @@ void UiUtils::fillRecvAddressesComboBoxHDWallet(QComboBox* comboBox comboBox->setCurrentIndex(0); } +void UiUtils::fillRecvAddressesComboBoxHDWallet(QComboBox* comboBox + , const std::vector& wallets) +{ + comboBox->clear(); + comboBox->addItem(QObject::tr("Auto Create")); + for (const auto& wd : wallets) { + for (auto addr : wd.addresses) { + comboBox->addItem(QString::fromStdString(addr.address.display())); + } + } + comboBox->setEnabled(true); + comboBox->setCurrentIndex(0); +} + QPixmap UiUtils::getQRCode(const QString& address, int size) { QRcode* code = QRcode_encodeString(address.toLatin1(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); @@ -419,12 +507,14 @@ double UiUtils::truncatePriceForAsset(double price, bs::network::Asset::Type at) multiplier = 10000; break; case bs::network::Asset::SpotXBT: + case bs::network::Asset::Future: multiplier = 100; break; case bs::network::Asset::PrivateMarket: multiplier = 1000000; break; default: + assert(false); return 0; } @@ -437,9 +527,13 @@ QString UiUtils::displayPriceForAssetType(double price, bs::network::Asset::Type case bs::network::Asset::SpotFX: return UiUtils::displayPriceFX(price); case bs::network::Asset::SpotXBT: + case bs::network::Asset::Future: return UiUtils::displayPriceXBT(price); case bs::network::Asset::PrivateMarket: return UiUtils::displayPriceCC(price); + default: + assert(false); + break; } return QString(); @@ -466,9 +560,13 @@ int UiUtils::GetPricePrecisionForAssetType(const bs::network::Asset::Type& asset case bs::network::Asset::SpotFX: return GetPricePrecisionFX(); case bs::network::Asset::SpotXBT: + case bs::network::Asset::Future: return GetPricePrecisionXBT(); case bs::network::Asset::PrivateMarket: return GetPricePrecisionCC(); + default: + assert(false); + break; } // Allow entering floating point numbers if the asset type was detected as Undefined @@ -496,9 +594,11 @@ static void getPrecsFor(const std::string &security, const std::string &product, valuePrec = UiUtils::GetAmountPrecisionFX(); break; case bs::network::Asset::Type::SpotXBT: + case bs::network::Asset::Type::Future: qtyPrec = UiUtils::GetAmountPrecisionXBT(); valuePrec = UiUtils::GetAmountPrecisionFX(); - if (security.substr(0, security.find('/')) != product) { + + if (product != XbtCurrencyString) { std::swap(qtyPrec, valuePrec); } break; @@ -507,6 +607,9 @@ static void getPrecsFor(const std::string &security, const std::string &product, // special case. display value for XBT with 6 decimals valuePrec = 6; break; + default: + assert(false); + break; } } @@ -545,9 +648,10 @@ QString UiUtils::displayShortAddress(const QString &addr, const uint maxLength) } -std::string UiUtils::getSelectedWalletId(QComboBox* comboBox) +std::string UiUtils::getSelectedWalletId(QComboBox* comboBox, int index) { - return comboBox->currentData(WalletIdRole).toString().toStdString(); + return comboBox->itemData(index, WalletIdRole).toString().toStdString(); + //return comboBox->currentData().toString().toStdString(); } UiUtils::WalletsTypes UiUtils::getSelectedWalletType(QComboBox* comboBox) @@ -690,7 +794,11 @@ ApplicationSettings::Setting UiUtils::limitRfqSetting(bs::network::Asset::Type t case bs::network::Asset::PrivateMarket : return ApplicationSettings::PmRfqLimit; + case bs::network::Asset::Future : + return ApplicationSettings::FuturesLimit; + default : + assert(false); return ApplicationSettings::FxRfqLimit; } } @@ -704,7 +812,10 @@ ApplicationSettings::Setting UiUtils::limitRfqSetting(const QString &name) } else if (name == QString::fromUtf8(bs::network::Asset::toString(bs::network::Asset::PrivateMarket))) { return ApplicationSettings::PmRfqLimit; + } else if (name == QString::fromUtf8(bs::network::Asset::toString(bs::network::Asset::Future))) { + return ApplicationSettings::FuturesLimit; } else { + assert(false); return ApplicationSettings::FxRfqLimit; } } @@ -721,7 +832,11 @@ QString UiUtils::marketNameForLimit(ApplicationSettings::Setting s) case ApplicationSettings::PmRfqLimit : return QObject::tr(bs::network::Asset::toString(bs::network::Asset::PrivateMarket)); + case ApplicationSettings::FuturesLimit : + return QObject::tr(bs::network::Asset::toString(bs::network::Asset::Future)); + default : + assert(false); return QString(); } } diff --git a/BlockSettleUILib/UiUtils.h b/BlockSettleUILib/UiUtils.h index d5c4a2205..86f5c3026 100644 --- a/BlockSettleUILib/UiUtils.h +++ b/BlockSettleUILib/UiUtils.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -22,6 +22,7 @@ #include "ApplicationSettings.h" #include "CommonTypes.h" #include "HDPath.h" +#include "Wallets/SignerDefs.h" QT_BEGIN_NAMESPACE class QAbstractItemModel; @@ -36,9 +37,7 @@ namespace bs { } } } -class AuthAddressManager; class BinaryData; -class PyBlockDataManager; class QComboBox; class QDateTime; class QPixmap; @@ -102,7 +101,7 @@ namespace UiUtils // return only number, product string is not included QString displayAmountForProduct(double quantity, const QString& product, bs::network::Asset::Type at); - QString displayDateTime(uint64_t time); + QString displayDateTime(uint32_t time); QString displayDateTime(const QDateTime& datetime); QString displayTimeMs(const QDateTime& datetime); @@ -134,17 +133,20 @@ namespace UiUtils All = Full | HardwareSW | WatchOnly, All_AllowHwLegacy = All | HardwareAll }; - int fillHDWalletsComboBox(QComboBox* comboBox, const std::shared_ptr& walletsManager + [[deprecated]] int fillHDWalletsComboBox(QComboBox* comboBox, const std::shared_ptr& walletsManager , int walletTypes); + int fillHDWalletsComboBox(QComboBox* comboBox, const std::vector&, int walletTypes); - void fillAuthAddressesComboBoxWithSubmitted(QComboBox* comboBox, const std::shared_ptr& authAddressManager); + void fillAuthAddressesComboBoxWithSubmitted(QComboBox* comboBox + , const std::vector &, int defaultIdx = 0); - void fillRecvAddressesComboBox(QComboBox* comboBox, const std::shared_ptr& targetWallet); - void fillRecvAddressesComboBoxHDWallet(QComboBox* comboBox + [[deprecated]] void fillRecvAddressesComboBox(QComboBox* comboBox, const std::shared_ptr& targetWallet); + [[deprecated]] void fillRecvAddressesComboBoxHDWallet(QComboBox* comboBox , const std::shared_ptr& targetHDWallet, bool showRegularWalletsOnly); + void fillRecvAddressesComboBoxHDWallet(QComboBox* comboBox, const std::vector& wallet); int selectWalletInCombobox(QComboBox* comboBox, const std::string& walletId, WalletsTypes type = WalletsTypes::None); - std::string getSelectedWalletId(QComboBox* comboBox); + std::string getSelectedWalletId(QComboBox* comboBox, int index); WalletsTypes getSelectedWalletType(QComboBox* comboBox); bs::hd::Purpose getSelectedHwPurpose(QComboBox* comboBox); @@ -163,9 +165,9 @@ namespace UiUtils QString modelPath(const QModelIndex &index, QAbstractItemModel *model); - extern const QLatin1String XbtCurrency; + extern const QString XbtCurrency; - double actualXbtPrice(bs::XBTAmount amount, double price); + double actualXbtPrice(bs::XBTAmount amount, double price); // FIXME: shouldn't be in UiUtils bs::hd::Purpose getHwWalletPurpose(WalletsTypes hwType); WalletsTypes getHwWalletType(bs::hd::Purpose purpose); diff --git a/BlockSettleUILib/UserScript.cpp b/BlockSettleUILib/UserScript.cpp deleted file mode 100644 index 9ae828109..000000000 --- a/BlockSettleUILib/UserScript.cpp +++ /dev/null @@ -1,514 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#include "UserScript.h" - -#include -#include -#include -#include -#include -#include - -#include "AssetManager.h" -#include "CurrencyPair.h" -#include "DataConnection.h" -#include "MDCallbacksQt.h" -#include "UiUtils.h" -#include "Wallets/SyncWalletsManager.h" -#include "UtxoReservationManager.h" - -UserScript::UserScript(const std::shared_ptr &logger, - const std::shared_ptr &mdCallbacks, const ExtConnections &extConns - , QObject* parent) - : QObject(parent) - , logger_(logger) - , engine_(new QQmlEngine(this)) - , component_(nullptr) - , md_(mdCallbacks ? new MarketData(mdCallbacks, this) : nullptr) - , extConns_(extConns) - , const_(new Constants(this)) - , storage_(new DataStorage(this)) -{ - if (md_) { - engine_->rootContext()->setContextProperty(QLatin1String("marketData"), md_); - } - engine_->rootContext()->setContextProperty(QLatin1String("constants"), const_); - engine_->rootContext()->setContextProperty(QLatin1String("dataStorage"), storage_); -} - -UserScript::~UserScript() -{ - delete component_; - component_ = nullptr; -} - -bool UserScript::load(const QString &filename) -{ - if (component_) component_->deleteLater(); - component_ = new QQmlComponent(engine_, QUrl::fromLocalFile(filename) - , QQmlComponent::PreferSynchronous, this); - if (!component_) { - logger_->error("Failed to load component for file {}", filename.toStdString()); - emit failed(tr("Failed to load script %1").arg(filename)); - return false; - } - - if (component_->isReady()) { - emit loaded(); - return true; - } - if (component_->isError()) { - logger_->error("Failed to load {}: {}", filename.toStdString() - , component_->errorString().toStdString()); - emit failed(component_->errorString()); - } - return false; -} - -QObject *UserScript::instantiate() -{ - auto rv = component_->create(); - if (!rv) { - emit failed(tr("Failed to instantiate: %1").arg(component_->errorString())); - } - return rv; -} - -void UserScript::setWalletsManager(std::shared_ptr walletsManager) -{ - const_->setWalletsManager(walletsManager); -} - -bool UserScript::sendExtConn(const QString &name, const QString &type, const QString &message) -{ - const auto &itConn = extConns_.find(name.toStdString()); - if (itConn == extConns_.end()) { - logger_->error("[UserScript::sendExtConn] can't find external connector {}" - , name.toStdString()); - return false; - } - if (!itConn->second->isActive()) { - logger_->error("[UserScript::sendExtConn] external connector {} is not " - "active", name.toStdString()); - return false; - } - QJsonParseError jsonError; - auto jsonDoc = QJsonDocument::fromJson(QByteArray::fromStdString(message.toStdString()) - , &jsonError); - if (jsonError.error != QJsonParseError::NoError) { - logger_->error("[UserScript::sendExtConn] invalid JSON message: {}\n{}" - , jsonError.errorString().toUtf8().toStdString(), message.toStdString()); - return false; - } - const auto messageObj = jsonDoc.object(); - QJsonObject jsonEnvelope; - jsonEnvelope[QLatin1Literal("to")] = name; - jsonEnvelope[QLatin1Literal("type")] = type; - jsonEnvelope[QLatin1Literal("message")] = messageObj; - jsonDoc.setObject(jsonEnvelope); - const auto &msgJSON = jsonDoc.toJson(QJsonDocument::JsonFormat::Compact).toStdString(); - - return itConn->second->send(msgJSON); -} - - -// -// MarketData -// - -MarketData::MarketData(const std::shared_ptr &mdCallbacks, QObject *parent) - : QObject(parent) -{ - connect(mdCallbacks.get(), &MDCallbacksQt::MDUpdate, this, &MarketData::onMDUpdated, - Qt::QueuedConnection); -} - -double MarketData::bid(const QString &sec) const -{ - auto it = data_.find(sec); - - if (it != data_.cend()) { - auto dit = it->second.find(bs::network::MDField::PriceBid); - - if (dit != it->second.cend()) { - return dit->second; - } else { - return 0.0; - } - } else { - return 0.0; - } -} - -double MarketData::ask(const QString &sec) const -{ - auto it = data_.find(sec); - - if (it != data_.cend()) { - auto dit = it->second.find(bs::network::MDField::PriceOffer); - - if (dit != it->second.cend()) { - return dit->second; - } else { - return 0.0; - } - } else { - return 0.0; - } -} - -void MarketData::onMDUpdated(bs::network::Asset::Type, const QString &security, - bs::network::MDFields data) -{ - for (const auto &field : data) { - data_[security][field.type] = field.value; - } -} - - -// -// DataStorage -// - -DataStorage::DataStorage(QObject *parent) - : QObject(parent) -{ -} - -double DataStorage::bought(const QString ¤cy) -{ - return std::accumulate(std::begin(bought_[currency]), std::end(bought_[currency]), - 0.0, - [] (double value, const std::map::value_type& p) - { return value + p.second; }); -} - -void DataStorage::setBought(const QString ¤cy, double v, const QString &id) -{ - bought_[currency][id] = v; -} - -double DataStorage::sold(const QString ¤cy) -{ - return std::accumulate(std::begin(sold_[currency]), std::end(sold_[currency]), - 0.0, - [] (double value, const std::map::value_type& p) - { return value + p.second; }); -} - -void DataStorage::setSold(const QString ¤cy, double v, const QString &id) -{ - sold_[currency][id] = v; -} - - -// -// Constants -// - -Constants::Constants(QObject *parent) - : QObject(parent) - , walletsManager_(nullptr) -{} - -int Constants::payInTxSize() const -{ - return 125; -} - -int Constants::payOutTxSize() const -{ - return 82; -} - -float Constants::feePerByte() -{ - if (walletsManager_) { - walletsManager_->estimatedFeePerByte(2, [this](float fee) { feePerByte_ = fee; }, this); - } - return feePerByte_; //NB: sometimes returns previous value if previous call needs to wait for result from Armory -} - -QString Constants::xbtProductName() const -{ - return UiUtils::XbtCurrency; -} - -void Constants::setWalletsManager(std::shared_ptr walletsManager) -{ - walletsManager_ = walletsManager; - walletsManager_->estimatedFeePerByte(2, [this](float fee) { feePerByte_ = fee; }, this); -} - - -AutoQuoter::AutoQuoter(const std::shared_ptr &logger - , const std::shared_ptr &assetManager - , const std::shared_ptr &mdCallbacks - , const ExtConnections &extConns, QObject* parent) - : UserScript(logger, mdCallbacks, extConns, parent) - , assetManager_(assetManager) -{ - qmlRegisterType("bs.terminal", 1, 0, "BSQuoteReqReply"); - qmlRegisterUncreatableType("bs.terminal", 1, 0, "BSQuoteRequest", tr("Can't create this type")); -} - -QObject *AutoQuoter::instantiate(const bs::network::QuoteReqNotification &qrn) -{ - QObject *rv = UserScript::instantiate(); - if (rv) { - BSQuoteReqReply *qrr = qobject_cast(rv); - qrr->init(logger_, assetManager_, this); - - BSQuoteRequest *qr = new BSQuoteRequest(rv); - qr->init(QString::fromStdString(qrn.quoteRequestId), QString::fromStdString(qrn.product) - , (qrn.side == bs::network::Side::Buy), qrn.quantity, static_cast(qrn.assetType)); - - qrr->setQuoteReq(qr); - qrr->setSecurity(QString::fromStdString(qrn.security)); - - connect(qrr, &BSQuoteReqReply::sendingQuoteReply, [this](const QString &reqId, double price) { - emit sendingQuoteReply(reqId, price); - }); - connect(qrr, &BSQuoteReqReply::pullingQuoteReply, [this](const QString &reqId) { - emit pullingQuoteReply(reqId); - }); - - qrr->start(); - } - return rv; -} - - -void BSQuoteRequest::init(const QString &reqId, const QString &product, bool buy, double qty, int at) -{ - requestId_ = reqId; - product_ = product; - isBuy_ = buy; - quantity_ = qty; - assetType_ = at; -} - -void BSQuoteReqReply::init(const std::shared_ptr &logger - , const std::shared_ptr &assetManager, UserScript *parent) -{ - logger_ = logger; - assetManager_ = assetManager; - parent_ = parent; -} - -void BSQuoteReqReply::log(const QString &s) -{ - logger_->info("[BSQuoteReqReply] {}", s.toStdString()); -} - -bool BSQuoteReqReply::sendQuoteReply(double price) -{ - QString reqId = quoteReq()->requestId(); - if (reqId.isEmpty()) return false; - emit sendingQuoteReply(reqId, price); - return true; -} - -bool BSQuoteReqReply::pullQuoteReply() -{ - QString reqId = quoteReq()->requestId(); - if (reqId.isEmpty()) return false; - emit pullingQuoteReply(reqId); - return true; -} - -QString BSQuoteReqReply::product() -{ - CurrencyPair cp(security().toStdString()); - return QString::fromStdString(cp.ContraCurrency(quoteReq()->product().toStdString())); -} - -double BSQuoteReqReply::accountBalance(const QString &product) -{ - return assetManager_->getBalance(product.toStdString(), bs::UTXOReservationManager::kIncludeZcRequestor, nullptr); -} - -bool BSQuoteReqReply::sendExtConn(const QString &name, const QString &type - , const QString &message) -{ - return parent_->sendExtConn(name, type, message); -} - - -void SubmitRFQ::stop() -{ - emit stopRFQ(id_.toStdString()); -} - - -void RFQScript::log(const QString &s) -{ - if (!logger_) { - return; - } - logger_->info("[RFQScript] {}", s.toStdString()); -} - -SubmitRFQ *RFQScript::sendRFQ(const QString &symbol, bool buy, double amount) -{ - const auto id = CryptoPRNG::generateRandom(8).toHexStr(); - const auto submitRFQ = new SubmitRFQ(this); - submitRFQ->setId(id); - submitRFQ->setSecurity(symbol); - submitRFQ->setAmount(amount); - submitRFQ->setBuy(buy); - - activeRFQs_[id] = submitRFQ; - emit sendingRFQ(submitRFQ); - return submitRFQ; -} - -void RFQScript::cancelRFQ(const std::string &id) -{ - const auto &itRFQ = activeRFQs_.find(id); - if (itRFQ == activeRFQs_.end()) { - logger_->error("[RFQScript::cancelRFQ] no active RFQ with id {}", id); - return; - } - emit cancellingRFQ(id); - itRFQ->second->deleteLater(); - activeRFQs_.erase(itRFQ); -} - -void RFQScript::cancelAll() -{ - for (const auto &rfq : activeRFQs_) { - emit cancellingRFQ(rfq.first); - rfq.second->deleteLater(); - } - activeRFQs_.clear(); -} - -void RFQScript::onMDUpdate(bs::network::Asset::Type, const QString &security, - bs::network::MDFields mdFields) -{ - if (!started_) { - return; - } - - auto &mdInfo = mdInfo_[security.toStdString()]; - mdInfo.merge(bs::network::MDField::get(mdFields)); - - for (const auto &rfq : activeRFQs_) { - const auto &sec = rfq.second->security(); - if (sec.isEmpty() || (security != sec)) { - continue; - } - if (mdInfo.bidPrice > 0) { - rfq.second->setIndicBid(mdInfo.bidPrice); - } - if (mdInfo.askPrice > 0) { - rfq.second->setIndicAsk(mdInfo.askPrice); - } - if (mdInfo.lastPrice > 0) { - rfq.second->setLastPrice(mdInfo.lastPrice); - } - } -} - -SubmitRFQ *RFQScript::activeRFQ(const QString &id) -{ - if (!started_) { - return nullptr; - } - const auto &itRFQ = activeRFQs_.find(id.toStdString()); - if (itRFQ == activeRFQs_.end()) { - return nullptr; - } - return itRFQ->second; -} - -void RFQScript::onAccepted(const std::string &id) -{ - if (!started_) { - return; - } - const auto &itRFQ = activeRFQs_.find(id); - if (itRFQ == activeRFQs_.end()) { - return; - } - emit accepted(QString::fromStdString(id)); - emit itRFQ->second->accepted(); -} - -void RFQScript::onExpired(const std::string &id) -{ - if (!started_) { - return; - } - const auto &itRFQ = activeRFQs_.find(id); - if (itRFQ == activeRFQs_.end()) { - logger_->warn("[RFQScript::onExpired] failed to find id {}", id); - return; - } - emit expired(QString::fromStdString(id)); - emit itRFQ->second->expired(); -} - -void RFQScript::onCancelled(const std::string &id) -{ - if (!started_) { - return; - } - const auto &itRFQ = activeRFQs_.find(id); - if (itRFQ == activeRFQs_.end()) { - return; - } - emit cancelled(QString::fromStdString(id)); - emit itRFQ->second->cancelled(); - itRFQ->second->deleteLater(); - activeRFQs_.erase(itRFQ); -} - - -AutoRFQ::AutoRFQ(const std::shared_ptr &logger - , const std::shared_ptr &mdCallbacks, QObject* parent) - : UserScript(logger, mdCallbacks, {}, parent) -{ - qRegisterMetaType(); - qmlRegisterType("bs.terminal", 1, 0, "SubmitRFQ"); - qmlRegisterType("bs.terminal", 1, 0, "RFQScript"); -} - -QObject *AutoRFQ::instantiate() -{ - QObject *rv = UserScript::instantiate(); - if (!rv) { - logger_->error("[AutoRFQ::instantiate] failed to instantiate script"); - return nullptr; - } - RFQScript *rfq = qobject_cast(rv); - if (!rfq) { - logger_->error("[AutoRFQ::instantiate] wrong script type"); - return nullptr; - } - rfq->init(logger_); - - connect(rfq, &RFQScript::sendingRFQ, this, &AutoRFQ::onSendRFQ); - connect(rfq, &RFQScript::cancellingRFQ, this, &AutoRFQ::cancelRFQ); - - return rv; -} - -void AutoRFQ::onSendRFQ(SubmitRFQ *rfq) -{ - if (!rfq) { - logger_->error("[AutoRFQ::onSendRFQ] no RFQ passed"); - return; - } - connect(rfq, &SubmitRFQ::stopRFQ, this, &AutoRFQ::stopRFQ); - emit sendRFQ(rfq->id().toStdString(), rfq->security(), rfq->amount(), rfq->buy()); -} diff --git a/BlockSettleUILib/UserScript.h b/BlockSettleUILib/UserScript.h deleted file mode 100644 index 53367b091..000000000 --- a/BlockSettleUILib/UserScript.h +++ /dev/null @@ -1,492 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -#ifndef __USER_SCRIPT_H__ -#define __USER_SCRIPT_H__ - -#include -#include -#include "CommonTypes.h" - -#include - -namespace spdlog { - class logger; -} -namespace bs { - namespace sync { - class WalletsManager; - } -} -class AssetManager; -class DataConnection; -class MDCallbacksQt; -class QQmlComponent; - - -// -// MarketData -// - -//! Market data for user script. -class MarketData : public QObject -{ - Q_OBJECT - -public: - MarketData(const std::shared_ptr &, QObject *parent); - ~MarketData() noexcept override = default; - - Q_INVOKABLE double bid(const QString &sec) const; - Q_INVOKABLE double ask(const QString &sec) const; - -private slots: - void onMDUpdated(bs::network::Asset::Type, const QString &security, bs::network::MDFields); - -private: - std::map> data_; -}; // class MarketData - - -// -// DataStorage -// - -//! Data storage for user script. -class DataStorage : public QObject -{ - Q_OBJECT - -public: - explicit DataStorage(QObject *parent); - ~DataStorage() noexcept override = default; - - Q_INVOKABLE double bought(const QString ¤cy); - Q_INVOKABLE void setBought(const QString ¤cy, double v, const QString &id); - - Q_INVOKABLE double sold(const QString ¤cy); - Q_INVOKABLE void setSold(const QString ¤cy, double v, const QString &id); - -private: - std::map> bought_; - std::map> sold_; -}; // class DataStorage - - -// -// Constants -// - -//! Useful constants for user script. -class Constants : public QObject -{ - Q_OBJECT - - Q_PROPERTY(int payInTxSize READ payInTxSize) // TODO: turn these to payInFeeEstimate and payOutFeeEstimate - Q_PROPERTY(int payOutTxSize READ payOutTxSize) - Q_PROPERTY(float feePerByte READ feePerByte) - Q_PROPERTY(QString xbtProductName READ xbtProductName) - -public: - explicit Constants(QObject *parent); - ~Constants() noexcept override = default; - - int payInTxSize() const; - int payOutTxSize() const; - float feePerByte(); - QString xbtProductName() const; - - void setWalletsManager(std::shared_ptr walletsManager); - -private: - std::shared_ptr walletsManager_; - mutable float feePerByte_ = 0.0; -}; // class Constants - -using ExtConnections = std::unordered_map>; - -class UserScript : public QObject -{ -Q_OBJECT - -public: - UserScript(const std::shared_ptr &, - const std::shared_ptr &, - const ExtConnections &, QObject* parent = nullptr); - ~UserScript() override; - - void setWalletsManager(std::shared_ptr walletsManager); - bool load(const QString &filename); - - bool sendExtConn(const QString &name, const QString &type, const QString &message); - -signals: - void loaded(); - void failed(const QString &desc); - -protected: - QObject *instantiate(); - -protected: - std::shared_ptr logger_; - QQmlEngine *engine_; - QQmlComponent *component_; - MarketData *md_; - ExtConnections extConns_; - Constants *const_; - DataStorage *storage_; -}; - - -class AutoQuoter : public UserScript -{ - Q_OBJECT -public: - AutoQuoter(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const ExtConnections &, QObject* parent = nullptr); - ~AutoQuoter() override = default; - - QObject *instantiate(const bs::network::QuoteReqNotification &qrn); - -signals: - void sendingQuoteReply(const QString &reqId, double price); - void pullingQuoteReply(const QString &reqId); - -private: - std::shared_ptr assetManager_; -}; - - -class BSQuoteRequest : public QObject -{ // Nested class for BSQuoteReqReply - not instantiatable - Q_OBJECT - Q_PROPERTY(QString requestId READ requestId) - Q_PROPERTY(QString product READ product) - Q_PROPERTY(bool isBuy READ isBuy) - Q_PROPERTY(double quantity READ quantity) - Q_PROPERTY(int assetType READ assetType) - -public: - explicit BSQuoteRequest(QObject *parent = nullptr) : QObject(parent) {} - ~BSQuoteRequest() override = default; - void init(const QString &reqId, const QString &product, bool buy, double qty, int at); - - QString requestId() const { return requestId_; } - QString product() const { return product_; } - bool isBuy() const { return isBuy_; } - double quantity() const { return quantity_; } - int assetType() const { return assetType_; } - - enum AssetType { - SpotFX = 1, - SpotXBT, - PrivateMarket - }; - - Q_ENUM(AssetType) - -private: - QString requestId_; - QString product_; - bool isBuy_; - double quantity_; - int assetType_; -}; - -class BSQuoteReqReply : public QObject -{ // Main QML-script facing class - Q_OBJECT - Q_PROPERTY(BSQuoteRequest* quoteReq READ quoteReq) - Q_PROPERTY(double expirationInSec READ expiration WRITE setExpiration NOTIFY expirationInSecChanged) - Q_PROPERTY(QString security READ security) - Q_PROPERTY(double indicBid READ indicBid NOTIFY indicBidChanged) - Q_PROPERTY(double indicAsk READ indicAsk NOTIFY indicAskChanged) - Q_PROPERTY(double lastPrice READ lastPrice NOTIFY lastPriceChanged) - Q_PROPERTY(double bestPrice READ bestPrice NOTIFY bestPriceChanged) - Q_PROPERTY(double isOwnBestPrice READ isOwnBestPrice) - -public: - explicit BSQuoteReqReply(QObject *parent = nullptr) : QObject(parent) {} //TODO: add dedicated AQ bs::Wallet - ~BSQuoteReqReply() override = default; - - void setQuoteReq(BSQuoteRequest *qr) { quoteReq_ = qr; } - BSQuoteRequest *quoteReq() const { return quoteReq_; } - - void setExpiration(double exp) { - if (exp != expirationInSec_) { - expirationInSec_ = exp; - emit expirationInSecChanged(); - } - } - double expiration() const { return expirationInSec_; } - - void setSecurity(const QString &security) { security_ = security; } - QString security() const { return security_; } - - void setIndicBid(double prc) { - if (indicBid_ != prc) { - indicBid_ = prc; - - if (started_) { - emit indicBidChanged(); - } - } - } - void setIndicAsk(double prc) { - if (indicAsk_ != prc) { - indicAsk_ = prc; - - if (started_) { - emit indicAskChanged(); - } - } - } - void setLastPrice(double prc) { - if (lastPrice_ != prc) { - lastPrice_ = prc; - - if (started_) { - emit lastPriceChanged(); - } - } - } - void setBestPrice(double prc, bool own) { - isOwnBestPrice_ = own; - if (bestPrice_ != prc) { - bestPrice_ = prc; - emit bestPriceChanged(); - } - } - double indicBid() const { return indicBid_; } - double indicAsk() const { return indicAsk_; } - double lastPrice() const { return lastPrice_; } - double bestPrice() const { return bestPrice_; } - bool isOwnBestPrice() const { return isOwnBestPrice_; } - - void init(const std::shared_ptr &logger - , const std::shared_ptr &assetManager, UserScript *parent); - - Q_INVOKABLE void log(const QString &); - Q_INVOKABLE bool sendQuoteReply(double price); - Q_INVOKABLE bool pullQuoteReply(); - Q_INVOKABLE QString product(); - Q_INVOKABLE double accountBalance(const QString &product); - Q_INVOKABLE bool sendExtConn(const QString &name, const QString &type, const QString &message); - - void start() { - if (!started_) { - started_ = true; - emit started(); - } - } - -signals: - void expirationInSecChanged(); - void indicBidChanged(); - void indicAskChanged(); - void lastPriceChanged(); - void bestPriceChanged(); - void sendingQuoteReply(const QString &reqId, double price); - void pullingQuoteReply(const QString &reqId); - void settled(); - void cancelled(); - void started(); - void extDataReceived(QString from, QString type, QString msg); - -private: - BSQuoteRequest *quoteReq_; - double expirationInSec_; - QString security_; - double indicBid_ = 0; - double indicAsk_ = 0; - double lastPrice_ = 0; - double bestPrice_ = 0; - bool isOwnBestPrice_ = false; - bool started_ = false; - std::shared_ptr logger_; - std::shared_ptr assetManager_; - UserScript * parent_; -}; - - -class SubmitRFQ : public QObject -{ // Container for individual RFQ submitted - Q_OBJECT - Q_PROPERTY(QString id READ id) - Q_PROPERTY(QString security READ security) - Q_PROPERTY(double amount READ amount WRITE setAmount NOTIFY amountChanged) - Q_PROPERTY(bool buy READ buy NOTIFY buyChanged) - Q_PROPERTY(double indicBid READ indicBid WRITE setIndicBid NOTIFY indicBidChanged) - Q_PROPERTY(double indicAsk READ indicAsk WRITE setIndicAsk NOTIFY indicAskChanged) - Q_PROPERTY(double lastPrice READ lastPrice WRITE setLastPrice NOTIFY lastPriceChanged) - -public: - explicit SubmitRFQ(QObject *parent = nullptr) : QObject(parent) {} - ~SubmitRFQ() override = default; - - void setId(const std::string &id) { id_ = QString::fromStdString(id); } - QString id() const { return id_; } - - void setSecurity(const QString &security) { security_ = security; } - QString security() const { return security_; } - - void setAmount(double amount) - { - if (amount_ != amount) { - amount_ = amount; - emit amountChanged(); - } - } - double amount() const { return amount_; } - - void setBuy(bool buy) - { - buy_ = buy; - emit buyChanged(); - } - bool buy() const { return buy_; } - - void setIndicBid(double prc) { - if (indicBid_ != prc) { - indicBid_ = prc; - emit indicBidChanged(); - } - } - void setIndicAsk(double prc) { - if (indicAsk_ != prc) { - indicAsk_ = prc; - emit indicAskChanged(); - } - } - void setLastPrice(double prc) { - if (lastPrice_ != prc) { - lastPrice_ = prc; - emit lastPriceChanged(); - } - } - double indicBid() const { return indicBid_; } - double indicAsk() const { return indicAsk_; } - double lastPrice() const { return lastPrice_; } - - Q_INVOKABLE void stop(); - -signals: - void amountChanged(); - void buyChanged(); - void indicBidChanged(); - void indicAskChanged(); - void lastPriceChanged(); - void stopRFQ(const std::string &id); - void cancelled(); - void accepted(); - void expired(); - -private: - QString id_; - QString security_; - double amount_ = 0; - bool buy_ = true; - double indicBid_ = 0; - double indicAsk_ = 0; - double lastPrice_ = 0; - std::shared_ptr assetManager_; -}; -Q_DECLARE_METATYPE(SubmitRFQ *) - - -class RFQScript : public QObject -{ // Main QML-script facing class - Q_OBJECT - -public: - explicit RFQScript(QObject *parent = nullptr) : QObject(parent) {} - ~RFQScript() override = default; - - void init(const std::shared_ptr &logger) - { - logger_ = logger; - } - - void start() { - if (!started_) { - started_ = true; - emit started(); - } - } - - void suspend() { - if (started_) { - started_ = false; - emit suspended(); - } - } - - Q_INVOKABLE void log(const QString &); - Q_INVOKABLE SubmitRFQ *sendRFQ(const QString &symbol, bool buy, double amount); - Q_INVOKABLE void cancelRFQ(const std::string &id); - Q_INVOKABLE SubmitRFQ *activeRFQ(const QString &id); - void cancelAll(); - - void onMDUpdate(bs::network::Asset::Type, const QString &security, - bs::network::MDFields mdFields); - - void onAccepted(const std::string &id); - void onExpired(const std::string &id); - void onCancelled(const std::string &id); - -signals: - void started(); - void suspended(); - void sendingRFQ(SubmitRFQ *); - void cancellingRFQ(const std::string &id); - - void indicBidChanged(const QString &security, double price); - void indicAskChanged(const QString &security, double price); - void lastPriceChanged(const QString &security, double price); - - void cancelled(const QString &id); - void accepted(const QString &id); - void expired(const QString &id); - -private: - bool started_ = false; - std::shared_ptr logger_; - std::unordered_map activeRFQs_; - - std::unordered_map mdInfo_; -}; - - -class AutoRFQ : public UserScript -{ - Q_OBJECT -public: - AutoRFQ(const std::shared_ptr & - , const std::shared_ptr & - , QObject* parent = nullptr); - ~AutoRFQ() override = default; - - QObject *instantiate(); - -private slots: - void onSendRFQ(SubmitRFQ *); - -signals: - void loaded(); - void failed(const QString &desc); - void sendRFQ(const std::string &id, const QString &symbol, double amount, bool buy); - void cancelRFQ(const std::string &id); - void stopRFQ(const std::string &id); -}; - - -#endif // __USER_SCRIPT_H__ diff --git a/BlockSettleUILib/UserScriptRunner.cpp b/BlockSettleUILib/UserScriptRunner.cpp deleted file mode 100644 index 533b67e99..000000000 --- a/BlockSettleUILib/UserScriptRunner.cpp +++ /dev/null @@ -1,739 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ - -#include "UserScriptRunner.h" -#include -#include -#include -#include -#include -#include "DataConnectionListener.h" -#include "MDCallbacksQt.h" -#include "SignContainer.h" -#include "UserScript.h" -#include "Wallets/SyncWalletsManager.h" - - -// -// UserScriptHandler -// - -UserScriptHandler::UserScriptHandler(const std::shared_ptr &logger) - :logger_(logger) -{} - -UserScriptHandler::~UserScriptHandler() noexcept = default; - -void UserScriptHandler::setParent(UserScriptRunner *runner) -{ - QObject::setParent(nullptr); - - connect(runner, &UserScriptRunner::init, this, &UserScriptHandler::init, - Qt::QueuedConnection); - connect(runner, &UserScriptRunner::deinit, this, &UserScriptHandler::deinit, - Qt::QueuedConnection); -} - -void UserScriptHandler::setWalletsManager(const std::shared_ptr &walletsManager) -{ - walletsManager_ = walletsManager; -} - - -AQScriptHandler::AQScriptHandler(const std::shared_ptr "eProvider - , const std::shared_ptr &signingContainer - , const std::shared_ptr &mdCallbacks - , const std::shared_ptr &assetManager - , const std::shared_ptr &logger) - : UserScriptHandler(logger) - , signingContainer_(signingContainer), mdCallbacks_(mdCallbacks) - , assetManager_(assetManager) - , aqEnabled_(false) - , aqTimer_(new QTimer(this)) -{ - connect(quoteProvider.get(), &QuoteProvider::quoteReqNotifReceived, - this, &AQScriptHandler::onQuoteReqNotification, Qt::QueuedConnection); - connect(quoteProvider.get(), &QuoteProvider::quoteNotifCancelled, - this, &AQScriptHandler::onQuoteNotifCancelled, Qt::QueuedConnection); - connect(quoteProvider.get(), &QuoteProvider::quoteCancelled, - this, &AQScriptHandler::onQuoteReqCancelled, Qt::QueuedConnection); - connect(quoteProvider.get(), &QuoteProvider::quoteRejected, - this, &AQScriptHandler::onQuoteReqRejected, Qt::QueuedConnection); - - connect(mdCallbacks_.get(), &MDCallbacksQt::MDUpdate, this, &AQScriptHandler::onMDUpdate, - Qt::QueuedConnection); - connect(quoteProvider.get(), &QuoteProvider::bestQuotePrice, - this, &AQScriptHandler::onBestQuotePrice, Qt::QueuedConnection); - - aqTimer_->setInterval(500); - connect(aqTimer_, &QTimer::timeout, this, &AQScriptHandler::aqTick); - aqTimer_->start(); -} - -AQScriptHandler::~AQScriptHandler() noexcept -{ - clear(); -} - -void AQScriptHandler::onThreadStopped() -{ - aqTimer_->stop(); - UserScriptHandler::onThreadStopped(); -} - -void AQScriptHandler::setWalletsManager(const std::shared_ptr &walletsManager) -{ - UserScriptHandler::setWalletsManager(walletsManager); - - if (aq_) { - aq_->setWalletsManager(walletsManager); - } -} - -void AQScriptHandler::setExtConnections(const ExtConnections &conns) -{ - if (conns.empty()) { - extConns_.clear(); - } - else { - extConns_ = conns; - } -} - -void AQScriptHandler::reload(const QString &filename) -{ - if (aq_) { - aq_->load(filename); - } -} - -void AQScriptHandler::onQuoteReqNotification(const bs::network::QuoteReqNotification &qrn) -{ - const auto &itAQObj = aqObjs_.find(qrn.quoteRequestId); - if ((qrn.status == bs::network::QuoteReqNotification::PendingAck) || (qrn.status == bs::network::QuoteReqNotification::Replied)) { - aqQuoteReqs_[qrn.quoteRequestId] = qrn; - if ((qrn.assetType != bs::network::Asset::SpotFX) && (!signingContainer_ || signingContainer_->isOffline())) { - logger_->error("[AQScriptHandler::onQuoteReqNotification] can't handle" - " non-FX quote without online signer"); - return; - } - if (aqEnabled_ && aq_ && (itAQObj == aqObjs_.end())) { - QObject *obj = aq_->instantiate(qrn); - aqObjs_[qrn.quoteRequestId] = obj; - if (thread_) { - obj->moveToThread(thread_); - } - - const auto &mdIt = mdInfo_.find(qrn.security); - auto reqReply = qobject_cast(obj); - if (!reqReply) { - logger_->error("[AQScriptHandler::onQuoteReqNotification] invalid AQ object instantiated"); - return; - } - if (mdIt != mdInfo_.end()) { - if (mdIt->second.bidPrice > 0) { - reqReply->setIndicBid(mdIt->second.bidPrice); - } - if (mdIt->second.askPrice > 0) { - reqReply->setIndicAsk(mdIt->second.askPrice); - } - if (mdIt->second.lastPrice > 0) { - reqReply->setLastPrice(mdIt->second.lastPrice); - } - } - reqReply->start(); - } - } - else if ((qrn.status == bs::network::QuoteReqNotification::Rejected) - || (qrn.status == bs::network::QuoteReqNotification::TimedOut)) { - if (itAQObj != aqObjs_.end()) { - const auto replyObj = qobject_cast(itAQObj->second); - if (replyObj) { - emit replyObj->cancelled(); - } - } - stop(qrn.quoteRequestId); - } -} - -void AQScriptHandler::stop(const std::string "eReqId) -{ - const auto &itAQObj = aqObjs_.find(quoteReqId); - aqQuoteReqs_.erase(quoteReqId); - if (itAQObj != aqObjs_.end()) { - itAQObj->second->deleteLater(); - aqObjs_.erase(itAQObj); - bestQPrices_.erase(quoteReqId); - } -} - -void AQScriptHandler::onQuoteReqCancelled(const QString &reqId, bool userCancelled) -{ - const auto itQR = aqQuoteReqs_.find(reqId.toStdString()); - if (itQR == aqQuoteReqs_.end()) { - return; - } - auto qrn = itQR->second; - - qrn.status = (userCancelled ? bs::network::QuoteReqNotification::Withdrawn : - bs::network::QuoteReqNotification::PendingAck); - onQuoteReqNotification(qrn); -} - -void AQScriptHandler::onQuoteNotifCancelled(const QString &reqId) -{ - onQuoteReqCancelled(reqId, true); -} - -void AQScriptHandler::onQuoteReqRejected(const QString &reqId, const QString &) -{ - onQuoteReqCancelled(reqId, true); -} - -void AQScriptHandler::init(const QString &fileName) -{ - if (fileName.isEmpty()) { - return; - } - aqEnabled_ = false; - - aq_ = new AutoQuoter(logger_, assetManager_, mdCallbacks_, extConns_, this); - if (walletsManager_) { - aq_->setWalletsManager(walletsManager_); - } - connect(aq_, &AutoQuoter::loaded, [this, fileName] { - emit scriptLoaded(fileName); - aqEnabled_ = true; - }); - connect(aq_, &AutoQuoter::failed, [this, fileName](const QString &err) { - logger_->error("Script loading failed: {}", err.toStdString()); - - aq_->deleteLater(); - aq_ = nullptr; - - emit failedToLoad(fileName, err); - }); - connect(aq_, &AutoQuoter::sendingQuoteReply, this, &AQScriptHandler::onAQReply); - connect(aq_, &AutoQuoter::pullingQuoteReply, this, &AQScriptHandler::onAQPull); - - aq_->load(fileName); -} - -void AQScriptHandler::deinit() -{ - clear(); - - if (aq_) { - aq_->deleteLater(); - aq_ = nullptr; - } -} - -void AQScriptHandler::clear() -{ - if (!aq_) { - return; - } - - for (auto aqObj : aqObjs_) { - aqObj.second->deleteLater(); - } - aqObjs_.clear(); - aqEnabled_ = false; - - std::vector requests; - for (const auto &aq : aqQuoteReqs_) { - switch (aq.second.status) { - case bs::network::QuoteReqNotification::PendingAck: - case bs::network::QuoteReqNotification::Replied: - requests.push_back(aq.first); - break; - case bs::network::QuoteReqNotification::Withdrawn: - case bs::network::QuoteReqNotification::Rejected: - case bs::network::QuoteReqNotification::TimedOut: - break; - case bs::network::QuoteReqNotification::StatusUndefined: - assert(false); - break; - } - } - - for (const std::string &reqId : requests) { - SPDLOG_LOGGER_INFO(logger_, "pull AQ request {}", reqId); - onAQPull(QString::fromStdString(reqId)); - } -} - -void AQScriptHandler::onMDUpdate(bs::network::Asset::Type, const QString &security, - bs::network::MDFields mdFields) -{ - auto &mdInfo = mdInfo_[security.toStdString()]; - mdInfo.merge(bs::network::MDField::get(mdFields)); - - for (auto aqObj : aqObjs_) { - QString sec = aqObj.second->property("security").toString(); - if (sec.isEmpty() || (security != sec)) { - continue; - } - - auto *reqReply = qobject_cast(aqObj.second); - if (mdInfo.bidPrice > 0) { - reqReply->setIndicBid(mdInfo.bidPrice); - } - if (mdInfo.askPrice > 0) { - reqReply->setIndicAsk(mdInfo.askPrice); - } - if (mdInfo.lastPrice > 0) { - reqReply->setLastPrice(mdInfo.lastPrice); - } - } -} - -void AQScriptHandler::onBestQuotePrice(const QString reqId, double price, bool own) -{ - const auto itAQObj = aqObjs_.find(reqId.toStdString()); - if (itAQObj != aqObjs_.end()) { - qobject_cast(itAQObj->second)->setBestPrice(price, own); - } -} - -void AQScriptHandler::onAQReply(const QString &reqId, double price) -{ - const auto itQRN = aqQuoteReqs_.find(reqId.toStdString()); - if (itQRN == aqQuoteReqs_.end()) { - logger_->warn("[UserScriptHandler::onAQReply] QuoteReqNotification with id = {} not found", reqId.toStdString()); - return; - } - - emit sendQuote(itQRN->second, price); -} - -void AQScriptHandler::onAQPull(const QString &reqId) -{ - const auto itQRN = aqQuoteReqs_.find(reqId.toStdString()); - if (itQRN == aqQuoteReqs_.end()) { - logger_->warn("[UserScriptHandler::onAQPull] QuoteReqNotification with id = {} not found", reqId.toStdString()); - return; - } - emit pullQuoteNotif(itQRN->second.settlementId, itQRN->second.quoteRequestId, itQRN->second.sessionToken); -} - -void AQScriptHandler::aqTick() -{ - if (aqObjs_.empty()) { - return; - } - QStringList expiredEntries; - const auto timeNow = QDateTime::currentDateTime(); - - for (auto aqObj : aqObjs_) { - BSQuoteRequest *qr = qobject_cast(aqObj.second)->quoteReq(); - if (!qr) continue; - auto itQRN = aqQuoteReqs_.find(qr->requestId().toStdString()); - if (itQRN == aqQuoteReqs_.end()) continue; - const auto timeDiff = timeNow.msecsTo(itQRN->second.expirationTime.addMSecs(itQRN->second.timeSkewMs)); - if (timeDiff <= 0) { - expiredEntries << qr->requestId(); - } - else { - aqObj.second->setProperty("expirationInSec", timeDiff / 1000.0); - } - } - for (const auto & expReqId : qAsConst(expiredEntries)) { - onQuoteReqCancelled(expReqId, true); - } -} - -void AQScriptHandler::performOnReplyAndStop(const std::string "eReqId - , const std::function &cb) -{ - const auto &itAQObj = aqObjs_.find(quoteReqId); - if (itAQObj != aqObjs_.end()) { - const auto replyObj = qobject_cast(itAQObj->second); - if (replyObj && cb) { - cb(replyObj); - } - } - QTimer::singleShot(1000, [this, quoteReqId] { stop(quoteReqId); }); -} - -void AQScriptHandler::cancelled(const std::string "eReqId) -{ - performOnReplyAndStop(quoteReqId, [](BSQuoteReqReply *replyObj) { - emit replyObj->cancelled(); - }); -} - -void AQScriptHandler::settled(const std::string "eReqId) -{ - performOnReplyAndStop(quoteReqId, [](BSQuoteReqReply *replyObj) { - emit replyObj->settled(); - }); -} - -void AQScriptHandler::extMsgReceived(const std::string &data) -{ - QJsonParseError jsonError; - const auto &jsonDoc = QJsonDocument::fromJson(QByteArray::fromStdString(data) - , &jsonError); - if (jsonError.error != QJsonParseError::NoError) { - logger_->error("[AQScriptHandler::extMsgReceived] invalid JSON message: {}" - , jsonError.errorString().toUtf8().toStdString()); - return; - } - const auto &jsonObj = jsonDoc.object(); - const auto &strFrom = jsonObj[QLatin1Literal("from")].toString(); - const auto &strType = jsonObj[QLatin1Literal("type")].toString(); - const auto &msgObj = jsonObj[QLatin1Literal("message")].toObject(); - QJsonDocument msgDoc(msgObj); - const auto &strMsg = QString::fromStdString(msgDoc.toJson(QJsonDocument::Compact).toStdString()); - if (strFrom.isEmpty() || strType.isEmpty() || msgObj.isEmpty()) { - logger_->error("[AQScriptHandler::extMsgReceived] invalid data in JSON: {}" - , data); - return; - } - - for (const auto &aqObj : aqObjs_) { - const auto replyObj = qobject_cast(aqObj.second); - if (replyObj) { - emit replyObj->extDataReceived(strFrom, strType, strMsg); - } - } -} - - -// -// UserScriptRunner -// -UserScriptRunner::UserScriptRunner(const std::shared_ptr &logger - , UserScriptHandler *script, QObject *parent) - : QObject(parent) - , thread_(new QThread(this)), script_(script), logger_(logger) -{ - script_->setParent(this); - connect(thread_, &QThread::finished, script_, &UserScriptHandler::onThreadStopped - , Qt::QueuedConnection); - script_->setRunningThread(thread_); - - connect(script_, &UserScriptHandler::scriptLoaded, this, &UserScriptRunner::scriptLoaded); - connect(script_, &UserScriptHandler::failedToLoad, this, &UserScriptRunner::failedToLoad); - - thread_->start(); -} - -UserScriptRunner::~UserScriptRunner() noexcept -{ - thread_->quit(); - thread_->wait(); -} - -void UserScriptRunner::setWalletsManager(const std::shared_ptr &walletsManager) -{ - script_->setWalletsManager(walletsManager); -} - -void UserScriptRunner::enable(const QString &fileName) -{ - logger_->info("Load script {}", fileName.toStdString()); - emit init(fileName); -} - -void UserScriptRunner::disable() -{ - logger_->info("Unload script"); - emit deinit(); -} - - -AQScriptRunner::AQScriptRunner(const std::shared_ptr "eProvider - , const std::shared_ptr &signingContainer - , const std::shared_ptr &mdCallbacks - , const std::shared_ptr &assetManager - , const std::shared_ptr &logger - , QObject *parent) - : UserScriptRunner(logger, new AQScriptHandler(quoteProvider, signingContainer, - mdCallbacks, assetManager, logger), parent) -{ - thread_->setObjectName(QStringLiteral("AQScriptRunner")); - - connect((AQScriptHandler *)script_, &AQScriptHandler::pullQuoteNotif, this - , &AQScriptRunner::pullQuoteNotif); - connect((AQScriptHandler *)script_, &AQScriptHandler::sendQuote, this - , &AQScriptRunner::sendQuote); -} - -AQScriptRunner::~AQScriptRunner() -{ - const auto aqHandler = qobject_cast(script_); - if (aqHandler) { - aqHandler->setExtConnections({}); - } -} - -void AQScriptRunner::cancelled(const std::string "eReqId) -{ - const auto aqHandler = qobject_cast(script_); - if (aqHandler) { - aqHandler->cancelled(quoteReqId); - } -} - -void AQScriptRunner::settled(const std::string "eReqId) -{ - const auto aqHandler = qobject_cast(script_); - if (aqHandler) { - aqHandler->settled(quoteReqId); - } -} - -class ExtConnListener : public DataConnectionListener -{ -public: - ExtConnListener(AQScriptRunner *parent, std::shared_ptr &logger) - : parent_(parent), logger_(logger) - {} - - void OnDataReceived(const std::string &data) override - { - parent_->onExtDataReceived(data); - } - - void OnConnected() override { logger_->debug("[{}]", __func__); } - void OnDisconnected() override { logger_->debug("[{}]", __func__); } - void OnError(DataConnectionError err) override { logger_->debug("[{}] {}", __func__, (int)err); } - -private: - AQScriptRunner *parent_{nullptr}; - std::shared_ptr logger_; -}; - -void AQScriptRunner::setExtConnections(const ExtConnections &conns) -{ - const auto aqHandler = qobject_cast(script_); - if (aqHandler) { - aqHandler->setExtConnections(conns); - } -} - -std::shared_ptr AQScriptRunner::getExtConnListener() -{ - if (!extConnListener_) { - extConnListener_ = std::make_shared(this, logger_); - } - return extConnListener_; -} - -void AQScriptRunner::onExtDataReceived(const std::string &data) -{ - const auto aqHandler = qobject_cast(script_); - if (aqHandler) { - aqHandler->extMsgReceived(data); - } -} - - -RFQScriptHandler::RFQScriptHandler(const std::shared_ptr &logger - , const std::shared_ptr &mdCallbacks) - : UserScriptHandler(logger) - , mdCallbacks_(mdCallbacks) -{ - connect(mdCallbacks_.get(), &MDCallbacksQt::MDUpdate, this, &RFQScriptHandler::onMDUpdate, - Qt::QueuedConnection); -} - -RFQScriptHandler::~RFQScriptHandler() noexcept -{ - clear(); -} - - -void RFQScriptHandler::setWalletsManager(const std::shared_ptr &walletsManager) -{ - UserScriptHandler::setWalletsManager(walletsManager); - - if (rfq_) { - rfq_->setWalletsManager(walletsManager); - } -} - -void RFQScriptHandler::reload(const QString &filename) -{ - if (rfq_) { - rfq_->load(filename); - } -} - -void RFQScriptHandler::init(const QString &fileName) -{ - if (fileName.isEmpty()) { - emit failedToLoad(fileName, tr("invalid script filename")); - return; - } - if (rfq_) { - start(); - return; // already inited - } - - rfq_ = new AutoRFQ(logger_, mdCallbacks_, this); - if (walletsManager_) { - rfq_->setWalletsManager(walletsManager_); - } - connect(rfq_, &AutoQuoter::loaded, [this, fileName] { - emit scriptLoaded(fileName); - }); - connect(rfq_, &AutoQuoter::failed, [this, fileName](const QString &err) { - logger_->error("Script loading failed: {}", err.toStdString()); - if (rfq_) { - rfq_->deleteLater(); - rfq_ = nullptr; - } - emit failedToLoad(fileName, err); - }); - - connect(rfq_, &AutoRFQ::sendRFQ, this, &RFQScriptHandler::sendRFQ); - connect(rfq_, &AutoRFQ::cancelRFQ, this, &RFQScriptHandler::cancelRFQ); - connect(rfq_, &AutoRFQ::stopRFQ, this, &RFQScriptHandler::onStopRFQ); - - if (!rfq_->load(fileName)) { - emit failedToLoad(fileName, tr("script loading failed")); - } - start(); -} - -void RFQScriptHandler::deinit() -{ - clear(); - - if (rfq_) { - rfq_->deleteLater(); - rfq_ = nullptr; - } -} - -void RFQScriptHandler::clear() -{ - if (!rfq_) { - return; - } - if (rfqObj_) { - rfqObj_->cancelAll(); - rfqObj_->deleteLater(); - rfqObj_ = nullptr; - } -} - -void RFQScriptHandler::onMDUpdate(bs::network::Asset::Type at, const QString &security, - bs::network::MDFields mdFields) -{ - if (!rfqObj_) { - return; - } - rfqObj_->onMDUpdate(at, security, mdFields); -} - -void RFQScriptHandler::start() -{ - if (!rfq_) { - return; - } - if (!rfqObj_) { - auto obj = rfq_->instantiate(); - if (!obj) { - emit failedToLoad({}, tr("failed to instantiate")); - return; - } - rfqObj_ = qobject_cast(obj); - if (!rfqObj_) { - logger_->error("[RFQScriptHandler::start] {} has wrong script object"); - emit failedToLoad({}, tr("wrong script loaded")); - return; - } - } - logger_->debug("[RFQScriptHandler::start]"); - rfqObj_->moveToThread(thread_); - rfqObj_->start(); -} - -void RFQScriptHandler::suspend() -{ - if (rfqObj_) { - rfqObj_->suspend(); - } -} - -void RFQScriptHandler::rfqAccepted(const std::string &id) -{ - if (rfqObj_) { - rfqObj_->onAccepted(id); - } -} - -void RFQScriptHandler::rfqCancelled(const std::string &id) -{ - if (rfqObj_) { - rfqObj_->onCancelled(id); - } -} - -void RFQScriptHandler::rfqExpired(const std::string &id) -{ - if (rfqObj_) { - rfqObj_->onExpired(id); - } -} - -void RFQScriptHandler::onStopRFQ(const std::string &id) -{ - if (rfqObj_) { - rfqObj_->onCancelled(id); - } -} - - -RFQScriptRunner::RFQScriptRunner(const std::shared_ptr &mdCallbacks - , const std::shared_ptr &logger - , QObject *parent) - : UserScriptRunner(logger, new RFQScriptHandler(logger, mdCallbacks), parent) -{ - thread_->setObjectName(QStringLiteral("RFQScriptRunner")); - - connect((RFQScriptHandler *)script_, &RFQScriptHandler::sendRFQ, this - , &RFQScriptRunner::sendRFQ); - connect((RFQScriptHandler *)script_, &RFQScriptHandler::cancelRFQ, this - , &RFQScriptRunner::cancelRFQ); -} - -RFQScriptRunner::~RFQScriptRunner() = default; - -void RFQScriptRunner::start(const QString &filename) -{ - emit init(filename); -} - -void RFQScriptRunner::suspend() -{ - ((RFQScriptHandler *)script_)->suspend(); -} - -void RFQScriptRunner::rfqAccepted(const std::string &id) -{ - ((RFQScriptHandler *)script_)->rfqAccepted(id); -} - -void RFQScriptRunner::rfqCancelled(const std::string &id) -{ - ((RFQScriptHandler *)script_)->rfqCancelled(id); -} - -void RFQScriptRunner::rfqExpired(const std::string &id) -{ - ((RFQScriptHandler *)script_)->rfqExpired(id); -} diff --git a/BlockSettleUILib/UserScriptRunner.h b/BlockSettleUILib/UserScriptRunner.h deleted file mode 100644 index a22fe632c..000000000 --- a/BlockSettleUILib/UserScriptRunner.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - -*********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ - -#ifndef USERSCRIPTRUNNER_H_INCLUDED -#define USERSCRIPTRUNNER_H_INCLUDED - -#include -#include - -#include -#include -#include -#include -#include - -#include "UserScript.h" -#include "QuoteProvider.h" -#include "CommonTypes.h" - -QT_BEGIN_NAMESPACE -class QThread; -QT_END_NAMESPACE - -namespace bs { - namespace sync { - class WalletsManager; - } -} -class DataConnectionListener; -class MDCallbacksQt; -class RFQScript; -class SignContainer; -class UserScriptRunner; - - -// -// UserScriptHandler -// - -//! Handler of events in user script. -class UserScriptHandler : public QObject -{ - Q_OBJECT -public: - explicit UserScriptHandler(const std::shared_ptr &); - ~UserScriptHandler() noexcept override; - - virtual void setWalletsManager(const std::shared_ptr &); - void setParent(UserScriptRunner *); - void setRunningThread(QThread *thread) { thread_ = thread; } - virtual void reload(const QString &filename) = 0; - -signals: - void scriptLoaded(const QString &fileName); - void failedToLoad(const QString &fileName, const QString &error); - -public slots: - virtual void onThreadStopped() - { - deleteLater(); - } - -protected slots: - virtual void init(const QString &fileName) {} - virtual void deinit() {} - -protected: - std::shared_ptr logger_; - std::shared_ptr walletsManager_; - QThread *thread_{nullptr}; -}; // class UserScriptHandler - - -class AQScriptHandler : public UserScriptHandler -{ - Q_OBJECT -public: - explicit AQScriptHandler(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); - ~AQScriptHandler() noexcept override; - - void setWalletsManager(const std::shared_ptr &) override; - void setExtConnections(const ExtConnections &conns); - void reload(const QString &filename) override; - - void cancelled(const std::string "eReqId); - void settled(const std::string "eReqId); - void extMsgReceived(const std::string &data); - -signals: - void pullQuoteNotif(const std::string& settlementId, const std::string& reqId, const std::string& reqSessToken); - void sendQuote(const bs::network::QuoteReqNotification &qrn, double price); - -public slots: - void onThreadStopped() override; - -protected slots: - void init(const QString &fileName) override; - void deinit() override; - -private slots: - void onQuoteReqNotification(const bs::network::QuoteReqNotification &qrn); - void onQuoteReqCancelled(const QString &reqId, bool userCancelled); - void onQuoteNotifCancelled(const QString &reqId); - void onQuoteReqRejected(const QString &reqId, const QString &); - void onMDUpdate(bs::network::Asset::Type, const QString &security, - bs::network::MDFields mdFields); - void onBestQuotePrice(const QString reqId, double price, bool own); - void onAQReply(const QString &reqId, double price); - void onAQPull(const QString &reqId); - void aqTick(); - -private: - void clear(); - void stop(const std::string "eReqId); - void performOnReplyAndStop(const std::string "eReqId - , const std::function &); - -private: - AutoQuoter *aq_ = nullptr; - std::shared_ptr signingContainer_; - std::shared_ptr mdCallbacks_; - std::shared_ptr assetManager_; - ExtConnections extConns_; - - std::unordered_map aqObjs_; - std::unordered_map aqQuoteReqs_; - std::unordered_map bestQPrices_; - - std::unordered_map mdInfo_; - - bool aqEnabled_; - QTimer *aqTimer_; -}; // class UserScriptHandler - - -class RFQScriptHandler : public UserScriptHandler -{ - Q_OBJECT -public: - explicit RFQScriptHandler(const std::shared_ptr & - , const std::shared_ptr &); - ~RFQScriptHandler() noexcept override; - - void setWalletsManager(const std::shared_ptr &) override; - void reload(const QString &filename) override; - - void suspend(); - void rfqAccepted(const std::string &id); - void rfqCancelled(const std::string &id); - void rfqExpired(const std::string &id); - -signals: - void sendRFQ(const std::string &id, const QString &symbol, double amount, bool buy); - void cancelRFQ(const std::string &id); - -protected slots: - void init(const QString &fileName) override; - void deinit() override; - -private slots: - void onMDUpdate(bs::network::Asset::Type, const QString &security, - bs::network::MDFields mdFields); - void onStopRFQ(const std::string &id); - -private: - void clear(); - void start(); - -private: - AutoRFQ *rfq_{ nullptr }; - std::shared_ptr mdCallbacks_; - RFQScript * rfqObj_{ nullptr }; -}; - - -class UserScriptRunner : public QObject -{ - Q_OBJECT -public: - UserScriptRunner(const std::shared_ptr & - , UserScriptHandler *, QObject *parent); - ~UserScriptRunner() noexcept override; - - void setWalletsManager(const std::shared_ptr &); - void setRunningThread(QThread *thread) { script_->setRunningThread(thread); } - void reload(const QString &filename) { script_->reload(filename); } - -signals: - void init(const QString &fileName); - void deinit(); - void stateChanged(bool enabled); - void scriptLoaded(const QString &fileName); - void failedToLoad(const QString &fileName, const QString &error); - -public slots: - void enable(const QString &fileName); - void disable(); - -protected: - QThread *thread_; - UserScriptHandler *script_; - std::shared_ptr logger_; -}; // class UserScriptRunner - -class AQScriptRunner : public UserScriptRunner -{ - Q_OBJECT -public: - AQScriptRunner(const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &, QObject *parent = nullptr); - ~AQScriptRunner() noexcept override; - - void cancelled(const std::string "eReqId); - void settled(const std::string "eReqId); - - void setExtConnections(const ExtConnections &); - std::shared_ptr getExtConnListener(); - void onExtDataReceived(const std::string &data); - -signals: - void pullQuoteNotif(const std::string& settlementId, const std::string& reqId, const std::string& reqSessToken); - void sendQuote(const bs::network::QuoteReqNotification &qrn, double price); - -private: - std::shared_ptr extConnListener_; -}; - -class RFQScriptRunner : public UserScriptRunner -{ - Q_OBJECT -public: - RFQScriptRunner(const std::shared_ptr &, - const std::shared_ptr &, - QObject *parent); - ~RFQScriptRunner() noexcept override; - - void start(const QString &file); - void suspend(); - void rfqAccepted(const std::string &id); - void rfqCancelled(const std::string &id); - void rfqExpired(const std::string &id); - -signals: - void sendRFQ(const std::string &id, const QString &symbol, double amount, bool buy); - void cancelRFQ(const std::string &id); -}; - -#endif // USERSCRIPTRUNNER_H_INCLUDED diff --git a/BlockSettleUILib/UtxoModelInterface.cpp b/BlockSettleUILib/UtxoModelInterface.cpp index d3bfbb1cb..4844757ca 100644 --- a/BlockSettleUILib/UtxoModelInterface.cpp +++ b/BlockSettleUILib/UtxoModelInterface.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/UtxoModelInterface.h b/BlockSettleUILib/UtxoModelInterface.h index 6551fa958..65463e67c 100644 --- a/BlockSettleUILib/UtxoModelInterface.h +++ b/BlockSettleUILib/UtxoModelInterface.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/UtxoReservationManager.cpp b/BlockSettleUILib/UtxoReservationManager.cpp index fb26f2822..d52b873b6 100644 --- a/BlockSettleUILib/UtxoReservationManager.cpp +++ b/BlockSettleUILib/UtxoReservationManager.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -18,8 +18,8 @@ #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" #include "Wallets/SyncHDLeaf.h" -#include "TradesUtils.h" -#include "ArmoryObject.h" +#include "Wallets/TradesUtils.h" +#include "Wallets/ArmoryObject.h" #include "WalletUtils.h" using namespace bs; @@ -232,36 +232,6 @@ void bs::UTXOReservationManager::getBestXbtUtxoSet(const HDWalletId& walletId, b getBestXbtFromUtxos(walletUtxos, quantity, std::move(cb), checkPbFeeFloor, checkAmount); } -BTCNumericTypes::balance_type bs::UTXOReservationManager::getAvailableCCUtxoSum(const CCProductName& CCProduct) const -{ - const auto& ccWallet = walletsManager_->getCCWallet(CCProduct); - if (!ccWallet) { - return {}; - } - - BTCNumericTypes::satoshi_type sum = 0; - auto const availableUtxos = getAvailableCCUTXOs(ccWallet->walletId()); - for (const auto &utxo : availableUtxos) { - sum += utxo.getValue(); - } - - return ccWallet->getTxBalance(sum); -} - -std::vector bs::UTXOReservationManager::getAvailableCCUTXOs(const CCWalletId& walletId) const -{ - std::vector utxos; - auto const availableUtxos = availableCCUTXOs_.find(walletId); - if (availableUtxos == availableCCUTXOs_.end()) { - return {}; - } - - utxos = availableUtxos->second; - std::vector filtered; - UtxoReservation::instance()->filter(utxos, filtered); - return utxos; -} - bs::FixedXbtInputs UTXOReservationManager::convertUtxoToFixedInput(const HDWalletId& walletId, const std::vector& utxos) { FixedXbtInputs fixedXbtInputs; @@ -471,7 +441,7 @@ void bs::UTXOReservationManager::getBestXbtFromUtxos(const std::vector &in QMetaObject::invokeMethod(mgr, [mgr, quantity, inputUtxo , fee, utxos = std::move(utxos), cb = std::move(cbCopy), checkPbFeeFloor, checkAmount]() mutable { - float feePerByte = ArmoryConnection::toFeePerByte(fee); + float feePerByte = BTCNumericTypes::toFeePerByte(fee); if (checkPbFeeFloor) { feePerByte = std::max(mgr->feeRatePb(), feePerByte); } diff --git a/BlockSettleUILib/UtxoReservationManager.h b/BlockSettleUILib/UtxoReservationManager.h index 254c5c3df..97689bc74 100644 --- a/BlockSettleUILib/UtxoReservationManager.h +++ b/BlockSettleUILib/UtxoReservationManager.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -84,7 +84,7 @@ namespace bs { BTCNumericTypes::satoshi_type getAvailableXbtUtxoSum(const HDWalletId& walletId, bool includeZc) const; BTCNumericTypes::satoshi_type getAvailableXbtUtxoSum(const HDWalletId& walletId, bs::hd::Purpose purpose, bool includeZc) const; - + std::vector getAvailableXbtUTXOs(const HDWalletId& walletId, bool includeZc) const; std::vector getAvailableXbtUTXOs(const HDWalletId& walletId, bs::hd::Purpose purpose, bool includeZc) const; @@ -93,10 +93,6 @@ namespace bs { std::function&&)>&& cb, bool checkPbFeeFloor, CheckAmount checkAmount, bool includeZc); void getBestXbtUtxoSet(const HDWalletId& walletId, bs::hd::Purpose purpose, BTCNumericTypes::satoshi_type quantity, std::function&&)>&& cb, bool checkPbFeeFloor, CheckAmount checkAmount); - - // CC specific implementation - BTCNumericTypes::balance_type getAvailableCCUtxoSum(const CCProductName& CCProduct) const; - std::vector getAvailableCCUTXOs(const CCWalletId& walletId) const; // Mutual functions FixedXbtInputs convertUtxoToFixedInput(const HDWalletId& walletId, const std::vector& utxos); diff --git a/BlockSettleUILib/UtxoReservationToken.cpp b/BlockSettleUILib/UtxoReservationToken.cpp index 0b476ed0b..8318e0cb8 100644 --- a/BlockSettleUILib/UtxoReservationToken.cpp +++ b/BlockSettleUILib/UtxoReservationToken.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/UtxoReservationToken.h b/BlockSettleUILib/UtxoReservationToken.h index 73218d3b0..68b71e6e3 100644 --- a/BlockSettleUILib/UtxoReservationToken.h +++ b/BlockSettleUILib/UtxoReservationToken.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/BlockSettleUILib/WalletsViewModel.cpp b/BlockSettleUILib/WalletsViewModel.cpp index 710903dc4..ee0a9b558 100644 --- a/BlockSettleUILib/WalletsViewModel.cpp +++ b/BlockSettleUILib/WalletsViewModel.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -14,13 +14,38 @@ #include #include -#include "SignContainer.h" +#include "Wallets/HeadlessContainer.h" #include "UiUtils.h" #include "ValidityFlag.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" +void WalletNode::remove(WalletNode* child) +{ + bool found = false; + for (auto& c : children_) { + if (!found && (child->id() == c->id())) { + found = true; + continue; + } + if (found) { + c->incRow(-1); + } + } + children_.removeOne(child); +} + +void WalletNode::replace(WalletNode *child) +{ + for (auto &c : children_) { + if (child->id() == c->id()) { + c = child; + break; + } + } +} + void WalletNode::clear() { qDeleteAll(children_); @@ -34,7 +59,7 @@ WalletNode *WalletNode::child(int index) const WalletNode *WalletNode::findByWalletId(const std::string &walletId) { - if ((type() == Type::Leaf) && (wallets()[0] != nullptr) && (wallets()[0]->walletId() == walletId)) { + if (id() == walletId) { return this; } for (const auto &child : children_) { @@ -46,10 +71,11 @@ WalletNode *WalletNode::findByWalletId(const std::string &walletId) return nullptr; } +class WalletGroupNode; class WalletRootNode : public WalletNode { public: - WalletRootNode(WalletsViewModel *vm, const std::shared_ptr &wallet + WalletRootNode(WalletsViewModel *vm, const bs::sync::WalletInfo &wallet , const std::string &name, const std::string &desc, WalletNode::Type type, int row , WalletNode *parent, BTCNumericTypes::balance_type balTotal = 0 , BTCNumericTypes::balance_type balUnconf = 0, BTCNumericTypes::balance_type balSpend = 0 @@ -104,7 +130,7 @@ class WalletRootNode : public WalletNode } std::string id() const override { - return (hdWallet_ ? hdWallet_->walletId() : std::string{}); + return (!hdWallet_.ids.empty() ? hdWallet_.ids[0] : std::string{}); } void setState(State state) override { @@ -114,51 +140,52 @@ class WalletRootNode : public WalletNode } } - void addGroups(const std::vector> &groups); +// void addGroups(const std::vector> &groups); + WalletGroupNode *addGroup(bs::core::wallet::Type, const std::string &name, const std::string &desc); - std::vector> wallets() const override + std::vector wallets() const override { - std::vector> ret = wallets_; + decltype(wallets_) ret = wallets_; - for (const auto * g : qAsConst(children_)) { + for (const auto *g : qAsConst(children_)) { const auto tmp = g->wallets(); - ret.insert(ret.end(), tmp.cbegin(), tmp.cend()); + ret.insert(ret.cend(), tmp.cbegin(), tmp.cend()); } return ret; } - BTCNumericTypes::balance_type getBalanceTotal() const { return balTotal_; } - BTCNumericTypes::balance_type getBalanceUnconf() const { return balUnconf_; } - BTCNumericTypes::balance_type getBalanceSpend() const { return balSpend_; } - size_t getNbUsedAddresses() const { return nbAddr_; } - std::shared_ptr hdWallet() const override { return hdWallet_; } + BTCNumericTypes::balance_type getBalanceTotal() const override { return balTotal_; } + BTCNumericTypes::balance_type getBalanceUnconf() const override { return balUnconf_; } + BTCNumericTypes::balance_type getBalanceSpend() const override { return balSpend_; } + size_t getNbUsedAddresses() const override { return nbAddr_; } + bs::sync::WalletInfo hdWallet() const override { return hdWallet_; } + + void updateCounters() + { + balTotal_ = 0; + balUnconf_ = 0; + balSpend_ = 0; + nbAddr_ = 0; + + for (const auto& child : children_) { + balTotal_.store(balTotal_.load() + child->getBalanceTotal()); + balUnconf_.store(balUnconf_.load() + child->getBalanceUnconf()); + balSpend_.store(balSpend_.load() + child->getBalanceSpend()); + + if (type() != Type::GroupCC) { + nbAddr_ += child->getNbUsedAddresses(); + } + } + } protected: std::string desc_; std::atomic balTotal_, balUnconf_, balSpend_; size_t nbAddr_; - std::shared_ptr hdWallet_; - std::vector> wallets_; + bs::sync::WalletInfo hdWallet_; + std::vector wallets_; protected: - void updateCounters(WalletRootNode *node) { - if (!node) { - return; - } - if (node->getBalanceTotal() > 0) { - balTotal_.store(balTotal_.load() + node->getBalanceTotal()); - } - if (node->getBalanceUnconf() > 0) { - balUnconf_.store(balUnconf_.load() + node->getBalanceUnconf()); - } - if (node->getBalanceSpend() > 0) { - balSpend_.store(balSpend_.load() + node->getBalanceSpend()); - } - if (type() != Type::GroupCC) { - nbAddr_ += node->getNbUsedAddresses(); - } - } - QString displayAmountOrLoading(BTCNumericTypes::balance_type balance) const { if (balance < 0) { return QObject::tr("Loading..."); @@ -207,12 +234,16 @@ class WalletRootNode : public WalletNode class WalletLeafNode : public WalletRootNode { public: - WalletLeafNode(WalletsViewModel *vm, const std::shared_ptr &wallet - , const std::shared_ptr &rootWallet, int row, WalletNode *parent) - : WalletRootNode(vm, rootWallet, wallet->shortName(), wallet->description(), Type::Leaf, row, parent - , 0, 0, 0, wallet->getUsedAddressCount()) + WalletLeafNode(WalletsViewModel *vm, const bs::sync::WalletInfo &wallet + , const bs::sync::WalletInfo &rootWallet, int row, WalletNode *parent) + : WalletRootNode(vm, rootWallet, wallet.name, wallet.description, Type::Leaf, row, parent + , 0, 0, 0, 0) , wallet_(wallet) + {} + + void setBalances(const std::shared_ptr &wallet) { + nbAddr_ = wallet->getUsedAddressCount(); wallet->onBalanceAvailable([this, wallet, handle = validityFlag_.handle()]() mutable { ValidityGuard lock(handle); if (!handle.isValid()) { @@ -224,15 +255,28 @@ class WalletLeafNode : public WalletRootNode }); } - std::vector> wallets() const override { return {wallet_}; } + void setBalances(size_t nbAddr, BTCNumericTypes::balance_type total + , BTCNumericTypes::balance_type spendable, BTCNumericTypes::balance_type unconfirmed) + { + nbAddr_ = nbAddr; + balTotal_ = total; + balSpend_ = spendable; + balUnconf_ = unconfirmed; + } - std::string id() const override { - return wallet_->walletId(); + std::vector wallets() const override + { + return { wallet_ }; + } + + std::string id() const override + { + return *wallet_.ids.cbegin(); } QVariant data(int col, int role) const override { if (role == Qt::FontRole) { - if (wallet_ == viewModel_->selectedWallet()) { + if (*wallet_.ids.cbegin() == viewModel_->selectedWallet()) { QFont font; font.setUnderline(true); return font; @@ -242,14 +286,14 @@ class WalletLeafNode : public WalletRootNode } private: - std::shared_ptr wallet_; + bs::sync::WalletInfo wallet_; ValidityFlag validityFlag_; }; class WalletGroupNode : public WalletRootNode { public: - WalletGroupNode(WalletsViewModel *vm, const std::shared_ptr &hdWallet + WalletGroupNode(WalletsViewModel *vm, const bs::sync::WalletInfo &hdWallet , const std::string &name, const std::string &desc, WalletNode::Type type , int row, WalletNode *parent) : WalletRootNode(vm, hdWallet, name, desc, type, row, parent) {} @@ -260,59 +304,57 @@ class WalletGroupNode : public WalletRootNode } } - std::vector> wallets() const override { return wallets_; } + std::vector wallets() const override { return wallets_; } - void addLeaves(const std::vector> &leaves) { - for (const auto &leaf : leaves) { - if (viewModel_->showRegularWallets() && (leaf->type() != bs::core::wallet::Type::Bitcoin || leaf->purpose() == bs::hd::Purpose::NonSegWit)) { - continue; - } - const auto leafNode = new WalletLeafNode(viewModel_, leaf, hdWallet_, nbChildren(), this); - add(leafNode); - updateCounters(leafNode); - wallets_.push_back(leaf); + void addLeaf(const bs::sync::WalletInfo &leaf, const std::shared_ptr &wallet) { + if (viewModel_->showRegularWallets() && (leaf.type != bs::core::wallet::Type::Bitcoin + || leaf.purpose == bs::hd::Purpose::NonSegWit)) { + return; + } + const auto leafNode = new WalletLeafNode(viewModel_, leaf, hdWallet_, nbChildren(), this); + leafNode->setBalances(wallet); + add(leafNode); + updateCounters(); + wallets_.push_back(leaf); + } + + void addLeaf(const bs::sync::HDWalletData::Leaf &leaf, bs::core::wallet::Type type) { + if (viewModel_->showRegularWallets() && (type != bs::core::wallet::Type::Bitcoin + || leaf.path.get(-2) == bs::hd::Purpose::NonSegWit)) { + return; } + bs::sync::WalletInfo wi; + wi.format = bs::sync::WalletFormat::Plain; + wi.ids = leaf.ids; + wi.name = leaf.name; + wi.description = leaf.description; + wi.purpose = static_cast(leaf.path.get(-2)); + const auto leafNode = new WalletLeafNode(viewModel_, wi, hdWallet_, nbChildren(), this); + add(leafNode); + wallets_.push_back(wi); } }; -void WalletRootNode::addGroups(const std::vector> &groups) +WalletGroupNode *WalletRootNode::addGroup(bs::core::wallet::Type type + , const std::string &name, const std::string &desc) { - for (const auto &group : groups) { - if (viewModel_->showRegularWallets() && (group->type() != bs::core::wallet::Type::Bitcoin)) { - continue; - } - const auto groupNode = new WalletGroupNode(viewModel_, hdWallet_, group->name(), group->description() - , getNodeType(group->type()), nbChildren(), this); - add(groupNode); - groupNode->addLeaves(group->getLeaves()); + if (viewModel_->showRegularWallets() && (type != bs::core::wallet::Type::Bitcoin)) { + return nullptr; } + const auto groupNode = new WalletGroupNode(viewModel_, hdWallet_, name, desc + , getNodeType(type), nbChildren(), this); + add(groupNode); + return groupNode; } -WalletsViewModel::WalletsViewModel(const std::shared_ptr &walletsManager - , const std::string &defaultWalletId, const std::shared_ptr &container +WalletsViewModel::WalletsViewModel(const std::string &defaultWalletId , QObject* parent, bool showOnlyRegular) : QAbstractItemModel(parent) - , walletsManager_(walletsManager) - , signContainer_(container) , defaultWalletId_(defaultWalletId) , showRegularWallets_(showOnlyRegular) { rootNode_ = std::make_shared(this, WalletNode::Type::Root); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletsReady, this, &WalletsViewModel::onWalletChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletChanged, this, &WalletsViewModel::onWalletChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletDeleted, this, &WalletsViewModel::onWalletChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::blockchainEvent, this, &WalletsViewModel::onWalletChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::invalidatedZCs, this, &WalletsViewModel::onWalletChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, &WalletsViewModel::onWalletChanged); - connect(walletsManager_.get(), &bs::sync::WalletsManager::newWalletAdded, this, &WalletsViewModel::onNewWalletAdded); - - if (signContainer_) { - connect(signContainer_.get(), &SignContainer::QWalletInfo, this, &WalletsViewModel::onWalletInfo); - connect(signContainer_.get(), &SignContainer::Error, this, &WalletsViewModel::onHDWalletError); - connect(signContainer_.get(), &SignContainer::authenticated, this, &WalletsViewModel::onSignerAuthenticated); - connect(signContainer_.get(), &SignContainer::ready, this, &WalletsViewModel::onWalletChanged); - } } WalletNode *WalletsViewModel::getNode(const QModelIndex &index) const @@ -333,7 +375,7 @@ int WalletsViewModel::rowCount(const QModelIndex &parent) const return getNode(parent)->nbChildren(); } -std::vector> WalletsViewModel::getWallets(const QModelIndex &index) const +std::vector WalletsViewModel::getWallets(const QModelIndex &index) const { const auto node = getNode(index); if (node == nullptr) { @@ -342,13 +384,13 @@ std::vector> WalletsViewModel::getWallets(cons return node->wallets(); } -std::shared_ptr WalletsViewModel::getWallet(const QModelIndex &index) const +bs::sync::WalletInfo WalletsViewModel::getWallet(const QModelIndex &index) const { const auto &wallets = getWallets(index); if (wallets.size() == 1) { return wallets[0]; } - return nullptr; + return {}; } QVariant WalletsViewModel::data(const QModelIndex &index, int role) const @@ -475,11 +517,6 @@ Qt::ItemFlags WalletsViewModel::flags(const QModelIndex &index) const return flags; } -std::shared_ptr WalletsViewModel::getAuthWallet() const -{ - return walletsManager_->getAuthWallet(); -} - static WalletNode::Type getHDWalletType(const std::shared_ptr &hdWallet , const std::shared_ptr &walletsMgr) { @@ -533,115 +570,127 @@ void WalletsViewModel::onSignerAuthenticated() continue; } const auto walletId = hdWallet->walletId(); - hdInfoReqIds_[signContainer_->GetInfo(walletId)] = walletId; + //TODO: hdInfoReqIds_[signContainer_->GetInfo(walletId)] = walletId; } } void WalletsViewModel::onNewWalletAdded(const std::string &walletId) { - if (!signContainer_) { - return; + //TODO:hdInfoReqIds_[signContainer_->GetInfo(walletId)] = walletId; +} + +void WalletsViewModel::onHDWallet(const bs::sync::WalletInfo &wi) +{ + const auto &wallet = rootNode_->findByWalletId(*wi.ids.cbegin()); + int row = wallet ? wallet->row() : rootNode_->nbChildren(); + const auto hdNode = new WalletRootNode(this, wi, wi.name, wi.description + , wi.primary ? WalletNode::Type::WalletPrimary : WalletNode::Type::WalletRegular + , row, rootNode_.get()); + if (wallet) { + rootNode_->replace(hdNode); + emit dataChanged(createIndex(row, 0, static_cast(rootNode_.get())) + , createIndex(row, (int)WalletColumns::ColumnCount - 1, static_cast(rootNode_.get()))); + } + else { + beginInsertRows(QModelIndex() + , rootNode_->nbChildren(), rootNode_->nbChildren()); + rootNode_->add(hdNode); + endInsertRows(); } - hdInfoReqIds_[signContainer_->GetInfo(walletId)] = walletId; + emit needHDWalletDetails(*wi.ids.cbegin()); } -void WalletsViewModel::LoadWallets(bool keepSelection) +void WalletsViewModel::onWalletDeleted(const bs::sync::WalletInfo& wi) { - const auto treeView = qobject_cast(QObject::parent()); - std::string selectedWalletId; - if (keepSelection && (treeView != nullptr)) { - selectedWalletId = "empty"; - const auto sel = treeView->selectionModel()->selectedRows(); - if (!sel.empty()) { - const auto fltModel = qobject_cast(treeView->model()); - const auto index = fltModel ? fltModel->mapToSource(sel[0]) : sel[0]; - const auto node = getNode(index); - if (node != nullptr) { - const auto &wallets = node->wallets(); - if (wallets.size() == 1) { - selectedWalletId = wallets[0]->walletId(); - } - } - } + const auto& wallet = rootNode_->findByWalletId(*wi.ids.cbegin()); + if (wallet) { + beginRemoveRows(QModelIndex(), wallet->row(), wallet->row()); + rootNode_->remove(wallet); + endRemoveRows(); } +} - beginResetModel(); - rootNode_->clear(); - for (const auto &hdWallet : walletsManager_->hdWallets()) { - if (!hdWallet) { +void WalletsViewModel::onHDWalletDetails(const bs::sync::HDWalletData &hdWallet) +{ + WalletNode *node{ nullptr }; + for (int i = 0; i < rootNode_->nbChildren(); ++i) { + if (rootNode_->child(i)->id() == hdWallet.id) { + node = rootNode_->child(i); + break; + } + } + const auto &hdNode = dynamic_cast(node); + if (!node || !hdNode) { + return; + } + for (const auto &group : hdWallet.groups) { + if (group.type == bs::hd::CoinType::BlockSettle_Settlement) { continue; } - const auto hdNode = new WalletRootNode(this, hdWallet, hdWallet->name(), hdWallet->description() - , getHDWalletType(hdWallet, walletsManager_), rootNode_->nbChildren(), rootNode_.get()); - rootNode_->add(hdNode); - - // filter groups - // don't display Settlement - auto groups = hdWallet->getGroups(); - std::vector> filteredGroups; - - std::copy_if(groups.begin(), groups.end(), std::back_inserter(filteredGroups), - [](const std::shared_ptr& item) - { return item->type() != bs::core::wallet::Type::Settlement; } - ); - - hdNode->addGroups(filteredGroups); - if (signContainer_) { - if (signContainer_->isOffline()) { - hdNode->setState(WalletNode::State::Offline); + WalletGroupNode *groupNode{ nullptr }; + for (int i = 0; i < node->nbChildren(); ++i) { + const auto &child = node->child(i); + if (child->name() == group.name) { + groupNode = dynamic_cast(child); + break; } - else if (hdWallet->isHardwareWallet()) { - hdNode->setState(WalletNode::State::Hardware); - } - else if (signContainer_->isWalletOffline(hdWallet->walletId())) { - hdNode->setState(WalletNode::State::Offline); - } - else if (hdWallet->isPrimary()) { - hdNode->setState(WalletNode::State::Primary); - } else { - hdNode->setState(WalletNode::State::Full); + } + bs::core::wallet::Type groupType{ bs::core::wallet::Type::Unknown }; + switch (group.type) { + case bs::hd::CoinType::Bitcoin_main: + case bs::hd::CoinType::Bitcoin_test: + groupType = bs::core::wallet::Type::Bitcoin; + break; + case bs::hd::CoinType::BlockSettle_Auth: + groupType = bs::core::wallet::Type::Authentication; + break; + case bs::hd::CoinType::BlockSettle_CC: + groupType = bs::core::wallet::Type::ColorCoin; + break; + default: break; + } + if (!groupNode) { + const auto& nbChildren = node->nbChildren(); + groupNode = hdNode->addGroup(groupType, group.name, group.description); + if (groupNode) { + beginInsertRows(createIndex(node->row(), 0, static_cast(node)) + , nbChildren, nbChildren); + endInsertRows(); } } - } - -/* const auto stmtWallet = walletsManager_->getSettlementWallet(); - if (!showRegularWallets() && (stmtWallet != nullptr)) { - const auto stmtNode = new WalletLeafNode(this, stmtWallet, rootNode_->nbChildren(), rootNode_.get()); - rootNode_->add(stmtNode); - }*/ //TODO: add later if decided - endResetModel(); - - QModelIndexList selection; - if (selectedWalletId.empty()) { - selectedWalletId = defaultWalletId_; - } - auto node = rootNode_->findByWalletId(selectedWalletId); - if (node != nullptr) { - selection.push_back(createIndex(node->row(), 0, static_cast(node))); - } - else if(rootNode_->hasChildren()) { - node = rootNode_->child(0); - selection.push_back(createIndex(node->row(), 0, static_cast(node))); - } - - if (treeView != nullptr) { - for (int i = 0; i < rowCount(); i++) { - treeView->expand(index(i, 0)); - // Expand XBT leaves - treeView->expand(index(0, 0, index(i, 0))); + if (!groupNode) { + continue; } - - if (!selection.empty()) { - treeView->setCurrentIndex(selection[0]); - treeView->selectionModel()->select(selection[0], QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - treeView->expand(selection[0]); - treeView->scrollTo(selection[0]); + for (const auto &leaf : group.leaves) { + const auto &leafNode = groupNode->findByWalletId(*leaf.ids.cbegin()); + if (!leafNode) { + beginInsertRows(createIndex(groupNode->row(), 0, static_cast(groupNode)) + , groupNode->nbChildren(), groupNode->nbChildren()); + groupNode->addLeaf(leaf, groupType); + endInsertRows(); + } + emit needWalletBalances(*leaf.ids.cbegin()); } } - emit updateAddresses(); } -void WalletsViewModel::onWalletChanged() +void WalletsViewModel::onWalletBalances(const bs::sync::WalletBalanceData &wbd) { - LoadWallets(true); + auto node = rootNode_->findByWalletId(wbd.id); + auto leafNode = dynamic_cast(node); + if (!node || !leafNode) { + return; + } + leafNode->setBalances(wbd.nbAddresses, wbd.balTotal, wbd.balSpendable, wbd.balUnconfirmed); + node = leafNode->parent(); + emit dataChanged(createIndex(leafNode->row(), (int)WalletColumns::ColumnSpendableBalance, static_cast(node)) + , createIndex(leafNode->row(), (int)WalletColumns::ColumnNbAddresses, static_cast(node))); + + auto groupNode = dynamic_cast(node); + if (!groupNode) { + return; + } + groupNode->updateCounters(); + emit dataChanged(createIndex(groupNode->row(), (int)WalletColumns::ColumnSpendableBalance, static_cast(groupNode->parent())) + , createIndex(leafNode->row(), (int)WalletColumns::ColumnNbAddresses, static_cast(groupNode->parent()))); } diff --git a/BlockSettleUILib/WalletsViewModel.h b/BlockSettleUILib/WalletsViewModel.h index aa4b1a48a..98f9fd9ca 100644 --- a/BlockSettleUILib/WalletsViewModel.h +++ b/BlockSettleUILib/WalletsViewModel.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -17,19 +17,16 @@ #include #include #include -#include "QWalletInfo.h" +#include "Wallets/QWalletInfo.h" +#include "Wallets/SignerDefs.h" namespace bs { namespace sync { - namespace hd { - class Wallet; - } - class Wallet; class WalletsManager; } } -class SignContainer; +class HeadlessContainer; class WalletsViewModel; @@ -59,22 +56,30 @@ class WalletNode : viewModel_(vm), parent_(parent), row_(row), type_(type) {} virtual ~WalletNode() { clear(); } - virtual std::vector> wallets() const { return {}; } - virtual std::shared_ptr hdWallet() const { return nullptr; } + virtual std::vector wallets() const { return {}; } + virtual bs::sync::WalletInfo hdWallet() const { return {}; } virtual QVariant data(int, int) const { return QVariant(); } virtual std::string id() const { return {}; } void add(WalletNode *child) { children_.append(child); } + void remove(WalletNode* child); + void replace(WalletNode *child); void clear(); int nbChildren() const { return children_.count(); } bool hasChildren() const { return !children_.empty(); } WalletNode *parent() const { return parent_; } WalletNode *child(int index) const; int row() const { return row_; } + void incRow(int increment = 1) { row_ += increment; } const std::string &name() const { return name_; } Type type() const { return type_; } State state() const { return state_; } + virtual void setState(State state) { state_ = state; } + virtual BTCNumericTypes::balance_type getBalanceTotal() const { return 0; } + virtual BTCNumericTypes::balance_type getBalanceUnconf() const { return 0; } + virtual BTCNumericTypes::balance_type getBalanceSpend() const { return 0; } + virtual size_t getNbUsedAddresses() const { return 0; } WalletNode *findByWalletId(const std::string &walletId); @@ -93,8 +98,7 @@ class WalletsViewModel : public QAbstractItemModel { Q_OBJECT public: - WalletsViewModel(const std::shared_ptr& walletsManager, const std::string &defaultWalletId - , const std::shared_ptr &sc = nullptr, QObject *parent = nullptr, bool showOnlyRegular = false); + WalletsViewModel(const std::string &defaultWalletId, QObject *parent = nullptr, bool showOnlyRegular = false); ~WalletsViewModel() noexcept override = default; WalletsViewModel(const WalletsViewModel&) = delete; @@ -102,16 +106,18 @@ Q_OBJECT WalletsViewModel(WalletsViewModel&&) = delete; WalletsViewModel& operator = (WalletsViewModel&&) = delete; - std::vector> getWallets(const QModelIndex &index) const; - std::shared_ptr getWallet(const QModelIndex &index) const; + std::vector getWallets(const QModelIndex &index) const; + bs::sync::WalletInfo getWallet(const QModelIndex &index) const; WalletNode *getNode(const QModelIndex &) const; - void setSelectedWallet(const std::shared_ptr &selWallet) { selectedWallet_ = selWallet; } + void setSelectedWallet(const std::string &selWallet) { selectedWalletId_ = selWallet; } - std::shared_ptr selectedWallet() const { return selectedWallet_; } + std::string selectedWallet() const { return selectedWalletId_; } bool showRegularWallets() const { return showRegularWallets_; } - std::shared_ptr getAuthWallet() const; - void LoadWallets(bool keepSelection = false); + void onHDWallet(const bs::sync::WalletInfo &); + void onWalletDeleted(const bs::sync::WalletInfo&); + void onHDWalletDetails(const bs::sync::HDWalletData &); + void onWalletBalances(const bs::sync::WalletBalanceData &); void setBitcoinLeafSelectionMode(bool flag = true) { bitcoinLeafSelectionMode_ = flag; } @@ -125,11 +131,13 @@ Q_OBJECT QModelIndex parent(const QModelIndex &child) const override; bool hasChildren(const QModelIndex& parent = QModelIndex()) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; + signals: void updateAddresses(); + void needHDWalletDetails(const std::string &walletId); + void needWalletBalances(const std::string &walletId); private slots: - void onWalletChanged(); void onNewWalletAdded(const std::string &walletId); void onWalletInfo(unsigned int id, bs::hd::WalletInfo); void onHDWalletError(unsigned int id, std::string err); @@ -161,8 +169,7 @@ private slots: private: std::shared_ptr walletsManager_; - std::shared_ptr signContainer_; - std::shared_ptr selectedWallet_; + std::string selectedWalletId_; std::shared_ptr rootNode_; std::string defaultWalletId_; bool showRegularWallets_; diff --git a/BlockSettleUILib/WalletsWidget.cpp b/BlockSettleUILib/WalletsWidget.cpp index 205b16a53..02d8b0a0f 100644 --- a/BlockSettleUILib/WalletsWidget.cpp +++ b/BlockSettleUILib/WalletsWidget.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -27,9 +27,11 @@ #include "ApplicationSettings.h" #include "AssetManager.h" #include "BSMessageBox.h" +#include "Wallets/HeadlessContainer.h" +#include "NewAddressDialog.h" #include "NewWalletDialog.h" -#include "SelectAddressDialog.h" -#include "SignContainer.h" +#include "SelectWalletDialog.h" +#include "Wallets/SignContainer.h" #include "WalletsViewModel.h" #include "WalletWarningDialog.h" #include "Wallets/SyncHDWallet.h" @@ -37,7 +39,7 @@ #include "TreeViewWithEnterKey.h" #include "ManageEncryption/RootWalletPropertiesDialog.h" -#include "SignerUiDefs.h" +#include "Wallets/SignerUiDefs.h" class AddressSortFilterModel : public QSortFilterProxyModel { @@ -143,7 +145,6 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(AddressSortFilterModel::Filter) WalletsWidget::WalletsWidget(QWidget* parent) : TabWithShortcut(parent) , ui_(new Ui::WalletsWidget()) - , walletsManager_(nullptr) , walletsModel_(nullptr) , addressModel_(nullptr) , addressSortFilterModel_(nullptr) @@ -159,9 +160,6 @@ WalletsWidget::WalletsWidget(QWidget* parent) actEditComment_ = new QAction(tr("&Edit Comment")); connect(actEditComment_, &QAction::triggered, this, &WalletsWidget::onEditAddrComment); - actRevokeSettl_ = new QAction(tr("&Revoke Settlement")); - connect(actRevokeSettl_, &QAction::triggered, this, &WalletsWidget::onRevokeSettlement); - // actDeleteWallet_ = new QAction(tr("&Delete Permanently")); // connect(actDeleteWallet_, &QAction::triggered, this, &WalletsWidget::onDeleteWallet); @@ -176,50 +174,30 @@ WalletsWidget::WalletsWidget(QWidget* parent) WalletsWidget::~WalletsWidget() = default; -void WalletsWidget::init(const std::shared_ptr &logger - , const std::shared_ptr &manager - , const std::shared_ptr &container - , const std::shared_ptr &applicationSettings - , const std::shared_ptr &connectionManager - , const std::shared_ptr &assetMgr - , const std::shared_ptr &authMgr - , const std::shared_ptr &armory) +void WalletsWidget::init(const std::shared_ptr &logger) { logger_ = logger; - walletsManager_ = manager; - signingContainer_ = container; - appSettings_ = applicationSettings; - assetManager_ = assetMgr; - authMgr_ = authMgr; - armory_ = armory; - connectionManager_ = connectionManager; - - // signingContainer_ might be null if user rejects remote signer key - if (signingContainer_) { - connect(signingContainer_.get(), &SignContainer::TXSigned, this, &WalletsWidget::onTXSigned); - } - const auto &defWallet = walletsManager_->getDefaultWallet(); - InitWalletsView(defWallet ? defWallet->walletId() : std::string{}); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletImportFinished, [this] { walletsModel_->LoadWallets(); }); +// const auto &defWallet = walletsManager_->getDefaultWallet(); +// InitWalletsView(defWallet ? defWallet->walletId() : std::string{}); - auto filter = appSettings_->get(ApplicationSettings::WalletFiltering); +/* auto filter = appSettings_->get(ApplicationSettings::WalletFiltering); ui_->pushButtonEmpty->setChecked(filter & AddressSortFilterModel::HideEmpty); ui_->pushButtonInternal->setChecked(filter & AddressSortFilterModel::HideInternal); ui_->pushButtonExternal->setChecked(filter & AddressSortFilterModel::HideExternal); ui_->pushButtonUsed->setChecked(filter & AddressSortFilterModel::HideUsedEmpty); - updateAddressFilters(filter); + updateAddressFilters(filter);*/ - for (auto button : {ui_->pushButtonEmpty, ui_->pushButtonInternal, - ui_->pushButtonExternal, ui_->pushButtonUsed}) { - connect(button, &QPushButton::toggled, this, &WalletsWidget::onFilterSettingsChanged); + InitWalletsView({}); + for (auto button : { ui_->pushButtonEmpty, ui_->pushButtonInternal, + ui_->pushButtonExternal, ui_->pushButtonUsed }) { + connect(button, &QPushButton::toggled, this, &WalletsWidget::onFilterSettingsChanged); } - - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletsSynchronized, this, &WalletsWidget::onWalletsSynchronized, Qt::QueuedConnection); } + void WalletsWidget::setUsername(const QString& username) { username_ = username; @@ -227,7 +205,10 @@ void WalletsWidget::setUsername(const QString& username) void WalletsWidget::InitWalletsView(const std::string& defaultWalletId) { - walletsModel_ = new WalletsViewModel(walletsManager_, defaultWalletId, signingContainer_, ui_->treeViewWallets); + walletsModel_ = new WalletsViewModel(defaultWalletId, ui_->treeViewWallets); + connect(walletsModel_, &WalletsViewModel::needHDWalletDetails, this, &WalletsWidget::needHDWalletDetails); + connect(walletsModel_, &WalletsViewModel::needWalletBalances, this, &WalletsWidget::needWalletBalances); + ui_->treeViewWallets->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui_->treeViewWallets->setModel(walletsModel_); ui_->treeViewWallets->setFocus(Qt::ActiveWindowFocusReason); @@ -236,7 +217,6 @@ void WalletsWidget::InitWalletsView(const std::string& defaultWalletId) ui_->treeViewWallets->setExpandsOnDoubleClick(false); // show the column as per BST-1520 //ui_->treeViewWallets->hideColumn(static_cast(WalletsViewModel::WalletColumns::ColumnID)); - walletsModel_->LoadWallets(); connect(ui_->walletPropertiesButton, &QPushButton::clicked, this, &WalletsWidget::showSelectedWalletProperties); connect(ui_->createWalletButton, &QPushButton::clicked, this, &WalletsWidget::onNewWallet); @@ -250,7 +230,12 @@ void WalletsWidget::InitWalletsView(const std::string& defaultWalletId) // No need to connect to wallet manager in AddressListModel explicitly in this case // so just put nullptr pointer in function - addressModel_ = new AddressListModel(nullptr, this); + addressModel_ = new AddressListModel(this); + connect(addressModel_, &AddressListModel::needExtAddresses, this, &WalletsWidget::needExtAddresses); + connect(addressModel_, &AddressListModel::needIntAddresses, this, &WalletsWidget::needIntAddresses); + connect(addressModel_, &AddressListModel::needUsedAddresses, this, &WalletsWidget::needUsedAddresses); + connect(addressModel_, &AddressListModel::needAddrComments, this, &WalletsWidget::needAddrComments); + addressSortFilterModel_ = new AddressSortFilterModel(this); addressSortFilterModel_->setSourceModel(addressModel_); addressSortFilterModel_->setSortRole(AddressListModel::SortRole); @@ -262,9 +247,9 @@ void WalletsWidget::InitWalletsView(const std::string& defaultWalletId) ui_->treeViewAddresses->header()->setSectionResizeMode(QHeaderView::ResizeToContents); updateAddresses(); - connect(ui_->treeViewWallets->selectionModel(), &QItemSelectionModel::selectionChanged, this, &WalletsWidget::updateAddresses); + connect(ui_->treeViewWallets->selectionModel(), &QItemSelectionModel::selectionChanged + , this, &WalletsWidget::updateAddresses); connect(walletsModel_, &WalletsViewModel::updateAddresses, this, &WalletsWidget::updateAddresses); - connect(walletsManager_.get(), &bs::sync::WalletsManager::walletBalanceUpdated, this, &WalletsWidget::onWalletBalanceChanged, Qt::QueuedConnection); connect(ui_->treeViewAddresses->model(), &QAbstractItemModel::layoutChanged, this, &WalletsWidget::treeViewAddressesLayoutChanged); connect(ui_->treeViewAddresses->selectionModel(), &QItemSelectionModel::selectionChanged, this, &WalletsWidget::treeViewAddressesSelectionChanged); @@ -283,19 +268,19 @@ WalletNode *WalletsWidget::getSelectedNode() const return nullptr; } -std::vector> WalletsWidget::getSelectedWallets() const +std::vector WalletsWidget::getSelectedWallets() const { const auto node = getSelectedNode(); - return node ? node->wallets() : std::vector>(); + return node ? node->wallets() : std::vector{}; } -std::shared_ptr WalletsWidget::getSelectedHdWallet() const +bs::sync::WalletInfo WalletsWidget::getSelectedHdWallet() const { const auto node = getSelectedNode(); - return node ? node->hdWallet() : nullptr; + return node ? node->hdWallet() : bs::sync::WalletInfo{}; } -std::vector> WalletsWidget::getFirstWallets() const +std::vector WalletsWidget::getFirstWallets() const { if (walletsModel_->rowCount()) { return walletsModel_->getWallets(walletsModel_->index(0, 0)); @@ -304,6 +289,159 @@ std::vector> WalletsWidget::getFirstWallets() } } +void WalletsWidget::onNewBlock(unsigned int blockNum) +{ + for (const auto &dlg : addrDetDialogs_) { + dlg.second->onNewBlock(blockNum); + } +} + +void WalletsWidget::onHDWallet(const bs::sync::WalletInfo &wi) +{ + wallets_.insert(wi); + walletsModel_->onHDWallet(wi); +} + +void WalletsWidget::onWalletDeleted(const bs::sync::WalletInfo& wi) +{ + if (rootDlg_) { + rootDlg_->walletDeleted(*wi.ids.cbegin()); + } + walletsModel_->onWalletDeleted(wi); + wallets_.erase(wi); +} + +void WalletsWidget::onHDWalletDetails(const bs::sync::HDWalletData &hdWallet) +{ + walletDetails_[hdWallet.id] = hdWallet; + if (rootDlg_) { + rootDlg_->onHDWalletDetails(hdWallet); + } + walletsModel_->onHDWalletDetails(hdWallet); + + for (int i = 0; i < walletsModel_->rowCount(); ++i) { + ui_->treeViewWallets->expand(walletsModel_->index(i, 0)); + // Expand XBT leaves + ui_->treeViewWallets->expand(walletsModel_->index(0, 0 + , walletsModel_->index(i, 0))); + } + + //TODO: keep wallets treeView selection +} + +void WalletsWidget::onGenerateAddress(bool isActive) +{ + if (wallets_.empty()) { + //TODO: invoke wallet create dialog + return; + } + + std::string selWalletId = curWalletId_; + if (!isActive || selWalletId.empty()) { + SelectWalletDialog selectWalletDialog(curWalletId_, this); + for (const auto& wallet : wallets_) { + selectWalletDialog.onHDWallet(wallet); + } + for (const auto& wallet : walletDetails_) { + selectWalletDialog.onHDWalletDetails(wallet.second); + } + for (const auto& bal : walletBalances_) { + selectWalletDialog.onWalletBalances(bal.second); + } + selectWalletDialog.exec(); + + if (selectWalletDialog.result() == QDialog::Rejected) { + return; + } + else { + selWalletId = selectWalletDialog.getSelectedWallet(); + } + } + bs::sync::WalletInfo selWalletInfo; + auto itWallet = walletDetails_.find(selWalletId); + if (itWallet == walletDetails_.end()) { + const auto& findLeaf = [selWalletId, wallets = walletDetails_]() -> bs::sync::WalletInfo + { + for (const auto& hdWallet : wallets) { + for (const auto& grp : hdWallet.second.groups) { + for (const auto& leaf : grp.leaves) { + for (const auto& id : leaf.ids) { + if (id == selWalletId) { + bs::sync::WalletInfo wi; + wi.name = leaf.name; + wi.ids = leaf.ids; + return wi; + } + } + } + } + } + return {}; + }; + selWalletInfo = findLeaf(); + if (selWalletInfo.ids.empty()) { + logger_->error("[{}] no leaf found for {}", __func__, selWalletId); + return; + } + } + else { + selWalletInfo.name = itWallet->second.name; + } + + if (newAddrDlg_) { + logger_->error("[{}] new address dialog already created", __func__); + return; + } + newAddrDlg_ = new NewAddressDialog(selWalletInfo, this); + emit createExtAddress(selWalletId); + connect(newAddrDlg_, &QDialog::finished, [this](int) { + newAddrDlg_->deleteLater(); + newAddrDlg_ = nullptr; + }); + newAddrDlg_->exec(); +} + +void WalletsWidget::onAddresses(const std::string &walletId + , const std::vector &addrs) +{ + if (newAddrDlg_) { + newAddrDlg_->onAddresses(walletId, addrs); + } + addressModel_->onAddresses(walletId, addrs); +} + +void WalletsWidget::onLedgerEntries(const std::string &filter, uint32_t totalPages + , uint32_t curPage, uint32_t curBlock, const std::vector &entries) +{ + const auto &itDlg = addrDetDialogs_.find(filter); + if (itDlg != addrDetDialogs_.end()) { + itDlg->second->onLedgerEntries(curBlock, entries); + } +} + +void WalletsWidget::onTXDetails(const std::vector &details) +{ + for (const auto &dlg : addrDetDialogs_) { + dlg.second->onTXDetails(details); + } +} + +void WalletsWidget::onAddressComments(const std::string &walletId + , const std::map &comments) +{ + addressModel_->onAddressComments(walletId, comments); +} + +void WalletsWidget::onWalletBalance(const bs::sync::WalletBalanceData &wbd) +{ + walletBalances_[wbd.id] = wbd; + if (rootDlg_) { + rootDlg_->onWalletBalances(wbd); + } + walletsModel_->onWalletBalances(wbd); + addressModel_->onAddressBalances(wbd.id, wbd.addrBalances); +} + void WalletsWidget::showSelectedWalletProperties() { auto indexes = ui_->treeViewWallets->selectionModel()->selectedIndexes(); @@ -325,29 +463,47 @@ void WalletsWidget::showWalletProperties(const QModelIndex& index) } const auto &hdWallet = node->hdWallet(); - if (hdWallet != nullptr) { - RootWalletPropertiesDialog(logger_, hdWallet, walletsManager_, armory_, signingContainer_ - , walletsModel_, appSettings_, connectionManager_, assetManager_, this).exec(); + if (!(*hdWallet.ids.cbegin()).empty()) { + rootDlg_ = new RootWalletPropertiesDialog(logger_, hdWallet, walletsModel_, this); + connect(rootDlg_, &RootWalletPropertiesDialog::needHDWalletDetails, this, &WalletsWidget::needHDWalletDetails); + connect(rootDlg_, &RootWalletPropertiesDialog::needWalletBalances, this, &WalletsWidget::needWalletBalances); + connect(rootDlg_, &RootWalletPropertiesDialog::needUTXOs, this, &WalletsWidget::needUTXOs); + connect(rootDlg_, &RootWalletPropertiesDialog::needWalletDialog, this, &WalletsWidget::needWalletDialog); + connect(rootDlg_, &QDialog::finished, [this](int) { + rootDlg_->deleteLater(); + rootDlg_ = nullptr; + }); + rootDlg_->exec(); } } void WalletsWidget::showAddressProperties(const QModelIndex& index) { auto sourceIndex = addressSortFilterModel_->mapToSource(index); - const auto walletId = addressModel_->data(sourceIndex, AddressListModel::WalletIdRole).toString().toStdString(); - const auto wallet = walletsManager_->getWalletById(walletId); - if (!wallet || (wallet->type() == bs::core::wallet::Type::Authentication)) { - return; + const auto &walletId = addressModel_->data(sourceIndex, AddressListModel::WalletIdRole).toString().toStdString(); + const auto &addrStr = addressModel_->data(sourceIndex, AddressListModel::AddressRole).toString().toStdString(); + const auto &ledgerFilter = walletId + "." + addrStr; + emit needLedgerEntries(ledgerFilter); + + const auto wltType = static_cast(addressModel_->data(sourceIndex, AddressListModel::WalletTypeRole).toInt()); + const auto txn = addressModel_->data(sourceIndex, AddressListModel::TxNRole).toInt(); + const uint64_t balance = addressModel_->data(sourceIndex, AddressListModel::BalanceRole).toULongLong(); + const auto &walletName = addressModel_->data(sourceIndex, AddressListModel::WalletNameRole).toString(); + const auto &path = addressModel_->data(sourceIndex, AddressListModel::AddressIndexRole).toString().toStdString(); + const auto &comment = addressModel_->data(sourceIndex, AddressListModel::AddressCommentRole).toString().toStdString(); + try { + const auto &address = bs::Address::fromAddressString(addrStr); + auto dialog = new AddressDetailDialog(address, logger_, wltType, balance + , txn, walletName, path, comment, this); + addrDetDialogs_[ledgerFilter] = dialog; + connect(dialog, &AddressDetailDialog::needTXDetails, this, &WalletsWidget::needTXDetails); + connect(dialog, &QDialog::finished, [dialog, ledgerFilter, this](int) { + dialog->deleteLater(); + addrDetDialogs_.erase(ledgerFilter); + }); + dialog->show(); } - - const auto &addresses = wallet->getUsedAddressList(); - const size_t addrIndex = addressModel_->data(sourceIndex, AddressListModel::AddrIndexRole).toUInt(); - const auto address = (addrIndex < addresses.size()) ? addresses[addrIndex] : bs::Address(); - - wallet->onBalanceAvailable([this, address, wallet] { - auto dialog = new AddressDetailDialog(address, wallet, walletsManager_, armory_, logger_, this); - QMetaObject::invokeMethod(this, [dialog] { dialog->exec(); }); - }); + catch (const std::exception &) {} } void WalletsWidget::onAddressContextMenu(const QPoint &p) @@ -361,33 +517,22 @@ void WalletsWidget::onAddressContextMenu(const QPoint &p) curAddress_.clear(); return; } - curWallet_ = walletsManager_->getWalletByAddress(curAddress_); - if (!curWallet_) { + curWalletId_ = addressModel_->data(addressIndex, AddressListModel::Role::WalletIdRole).toString().toStdString(); + if (curWalletId_.empty()) { logger_->warn("Failed to find wallet for address {}", curAddress_.display()); return; } + curComment_ = addressModel_->data(addressIndex, AddressListModel::Role::AddressCommentRole).toString().toStdString(); auto contextMenu = new QMenu(this); - if ((curWallet_->type() == bs::core::wallet::Type::Bitcoin) || (getSelectedWallets().size() == 1)) { + if ((static_cast(addressModel_->data(addressIndex, AddressListModel::Role::WalletTypeRole).toInt()) + == bs::core::wallet::Type::Bitcoin) || (getSelectedWallets().size() == 1)) { contextMenu->addAction(actCopyAddr_); } contextMenu->addAction(actEditComment_); - const auto &cbAddrBalance = [this, p, contextMenu](std::vector balances) - { - if (/*(curWallet_ == walletsManager_->getSettlementWallet()) &&*/ walletsManager_->getAuthWallet() - /*&& (curWallet_->getAddrTxN(curAddress_) == 1)*/ && balances[0]) { - contextMenu->addAction(actRevokeSettl_); - } - emit showContextMenu(contextMenu, ui_->treeViewAddresses->mapToGlobal(p)); - }; - - auto balanceVec = curWallet_->getAddrBalance(curAddress_); - if (balanceVec.size() == 0) - emit showContextMenu(contextMenu, ui_->treeViewAddresses->mapToGlobal(p)); - else - cbAddrBalance(balanceVec); + contextMenu ->exec(ui_->treeViewAddresses->mapToGlobal(p)); } void WalletsWidget::onShowContextMenu(QMenu *menu, QPoint where) @@ -417,7 +562,13 @@ void WalletsWidget::updateAddresses() if (ui_->treeViewWallets->selectionModel()->hasSelection()) { prevSelectedWalletRow_ = ui_->treeViewWallets->selectionModel()->selectedIndexes().first().row(); } - + + if (selectedWallets.size() == 1) { + curWalletId_ = *selectedWallets.at(0).ids.cbegin(); + } + else { + curWalletId_.clear(); + } prevSelectedWallets_ = selectedWallets; if (!applyPreviousSelection()) { @@ -490,30 +641,12 @@ void WalletsWidget::scrollChanged() } } -void WalletsWidget::onWalletsSynchronized() -{ - if (walletsManager_->hasPrimaryWallet()) { - int i = 0; - for (const auto &hdWallet : walletsManager_->hdWallets()) { - if (hdWallet->isPrimary()) { - ui_->treeViewWallets->selectionModel()->select(walletsModel_->index(i, 0) - , QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - break; - } - i++; - } - } - else if (!walletsManager_->hdWallets().empty()){ - ui_->treeViewWallets->selectionModel()->select(walletsModel_->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - } -} - void WalletsWidget::onWalletBalanceChanged(std::string walletId) { const auto &selectedWallets = getSelectedWallets(); bool changedSelected = false; for (const auto &wallet : selectedWallets) { - if (wallet->walletId() == walletId) { + if (*wallet.ids.cbegin() == walletId) { changedSelected = true; break; } @@ -525,47 +658,39 @@ void WalletsWidget::onWalletBalanceChanged(std::string walletId) void WalletsWidget::onNewWallet() { - if (!signingContainer_) { - showError(tr("Signer not created (yet)")); - return; - } - if (!signingContainer_->isOffline()) { - NewWalletDialog newWalletDialog(false, appSettings_, this); - emit newWalletCreationRequest(); - - int rc = newWalletDialog.exec(); - - switch (rc) { - case NewWalletDialog::CreateNew: - CreateNewWallet(); - break; - case NewWalletDialog::ImportExisting: - ImportNewWallet(); - break; - case NewWalletDialog::ImportHw: - ImportHwWallet(); - break; - case NewWalletDialog::Cancel: - break; - } - } else { + emit newWalletCreationRequest(); + NewWalletDialog newWalletDialog(false, this); + switch (newWalletDialog.exec()) { + case NewWalletDialog::CreateNew: + CreateNewWallet(); + break; + case NewWalletDialog::ImportExisting: ImportNewWallet(); + break; + case NewWalletDialog::ImportHw: + ImportHwWallet(); + break; + case NewWalletDialog::Cancel: + break; + default: + showError(tr("Unknown new wallet choice")); + break; } } void WalletsWidget::CreateNewWallet() { - signingContainer_->customDialogRequest(bs::signer::ui::GeneralDialogType::CreateWallet); + emit needWalletDialog(bs::signer::ui::GeneralDialogType::CreateWallet); } void WalletsWidget::ImportNewWallet() { - signingContainer_->customDialogRequest(bs::signer::ui::GeneralDialogType::ImportWallet); + emit needWalletDialog(bs::signer::ui::GeneralDialogType::ImportWallet); } void WalletsWidget::ImportHwWallet() { - signingContainer_->customDialogRequest(bs::signer::ui::GeneralDialogType::ImportHwWallet); + emit needWalletDialog(bs::signer::ui::GeneralDialogType::ImportHwWallet); } void WalletsWidget::shortcutActivated(ShortcutType s) @@ -589,9 +714,9 @@ void WalletsWidget::shortcutActivated(ShortcutType s) void WalletsWidget::onFilterSettingsChanged() { auto filterSettings = getUIFilterSettings(); - - appSettings_->set(ApplicationSettings::WalletFiltering, filterSettings); - + if (appSettings_) { + appSettings_->set(ApplicationSettings::WalletFiltering, filterSettings); + } updateAddressFilters(filterSettings); } @@ -650,26 +775,20 @@ void WalletsWidget::onCopyAddress() void WalletsWidget::onEditAddrComment() { - if (!curWallet_ || curAddress_.empty()) { + if (curWalletId_.empty() || curAddress_.empty()) { return; } bool isOk = false; - const auto oldComment = curWallet_->getAddressComment(curAddress_); + std::string oldComment; + oldComment = curComment_; const auto comment = QInputDialog::getText(this, tr("Edit Comment") , tr("Enter new comment for address %1:").arg(QString::fromStdString(curAddress_.display())) , QLineEdit::Normal, QString::fromStdString(oldComment), &isOk); if (isOk) { - if (!curWallet_->setAddressComment(curAddress_, comment.toStdString())) { - BSMessageBox(BSMessageBox::critical, tr("Address Comment"), tr("Failed to save comment")).exec(); - } + emit setAddrComment(curWalletId_, curAddress_, comment.toStdString()); } } -void WalletsWidget::onRevokeSettlement() -{ - BSMessageBox(BSMessageBox::info, tr("Settlement Revoke"), tr("Doesn't work currently"), this).exec(); -} - void WalletsWidget::onTXSigned(unsigned int id, BinaryData signedTX, bs::error::ErrorCode result) { if (!revokeReqId_ || (revokeReqId_ != id)) { @@ -683,20 +802,8 @@ void WalletsWidget::onTXSigned(unsigned int id, BinaryData signedTX, bs::error:: } if (!armory_->broadcastZC(signedTX).empty()) { -// walletsManager_->getSettlementWallet()->setTransactionComment(signedTX, "Settlement Revoke"); //TODO later } else { BSMessageBox(BSMessageBox::critical, title, tr("Failed to send transaction to mempool")).exec(); } } - - // Not used -//void WalletsWidget::onDeleteWallet() -//{ -// const auto action = qobject_cast(sender()); -// const auto walletId = action ? action->data().toString() : QString(); -// if (walletId.isEmpty()) { -// BSMessageBox(BSMessageBox::critical, tr("Wallet Delete"), tr("Failed to delete wallet"), this).exec(); -// return; -// } -//} diff --git a/BlockSettleUILib/WalletsWidget.h b/BlockSettleUILib/WalletsWidget.h index fa03be787..46c0367ed 100644 --- a/BlockSettleUILib/WalletsWidget.h +++ b/BlockSettleUILib/WalletsWidget.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -16,6 +16,8 @@ #include #include #include "Address.h" +#include "Wallets/SignerDefs.h" +#include "Wallets/SignerUiDefs.h" #include "TabWithShortcut.h" #include "BSErrorCode.h" #include "BSErrorCodeStrings.h" @@ -36,6 +38,7 @@ namespace bs { class WalletsManager; } } +class AddressDetailDialog; class AddressListModel; class AddressSortFilterModel; class ApplicationSettings; @@ -43,8 +46,11 @@ class ArmoryConnection; class AssetManager; class AuthAddressManager; class ConnectionManager; +class HeadlessContainer; class QAction; class QMenu; +class NewAddressDialog; +class RootWalletPropertiesDialog; class SignContainer; class WalletNode; class WalletsViewModel; @@ -57,26 +63,32 @@ Q_OBJECT WalletsWidget(QWidget* parent = nullptr ); ~WalletsWidget() override; - void init(const std::shared_ptr &logger - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &connectionManager - , const std::shared_ptr & - , const std::shared_ptr & - , const std::shared_ptr &); + void init(const std::shared_ptr &logger); void setUsername(const QString& username); WalletNode *getSelectedNode() const; - std::vector> getSelectedWallets() const; - std::vector> getFirstWallets() const; - std::shared_ptr getSelectedHdWallet() const; + std::vector getSelectedWallets() const; + std::vector getFirstWallets() const; + bs::sync::WalletInfo getSelectedHdWallet() const; void CreateNewWallet(); void ImportNewWallet(); void ImportHwWallet(); + void onNewBlock(unsigned int blockNum); + void onHDWallet(const bs::sync::WalletInfo &); + void onWalletDeleted(const bs::sync::WalletInfo&); + void onHDWalletDetails(const bs::sync::HDWalletData &); + void onGenerateAddress(bool isActive); + void onAddresses(const std::string& walletId, const std::vector &); + void onAddressComments(const std::string &walletId + , const std::map &); + void onWalletBalance(const bs::sync::WalletBalanceData &); + void onLedgerEntries(const std::string &filter, uint32_t totalPages + , uint32_t curPage, uint32_t curBlock, const std::vector &); + void onTXDetails(const std::vector &); + void shortcutActivated(ShortcutType s) override; public slots: @@ -94,8 +106,24 @@ public slots: bool filterBtcOnly() const; signals: - void showContextMenu(QMenu *, QPoint); + void showContextMenu(QMenu *, QPoint); // deprecated void newWalletCreationRequest(); + void needHDWalletDetails(const std::string &walletId); + void needWalletBalances(const std::string &walletId); + void needUTXOs(const std::string& id, const std::string& walletId + , bool confOnly = false, bool swOnly = false); + void needExtAddresses(const std::string &walletId); + void needIntAddresses(const std::string &walletId); + void needUsedAddresses(const std::string &walletId); + void needAddrComments(const std::string &walletId, const std::vector &); + void setAddrComment(const std::string &walletId, const bs::Address & + , const std::string &comment); + void needLedgerEntries(const std::string &filter); + void needTXDetails(const std::vector &, bool useCache + , const bs::Address &); + void needWalletDialog(bs::signer::ui::GeneralDialogType + , const std::string& rootId = {}); + void createExtAddress(const std::string& walletId); private slots: void showWalletProperties(const QModelIndex& index); @@ -106,7 +134,6 @@ private slots: //void onWalletContextMenu(const QPoint &); void onCopyAddress(); void onEditAddrComment(); - void onRevokeSettlement(); void onTXSigned(unsigned int id, BinaryData signedTX, bs::error::ErrorCode result); //void onDeleteWallet(); void onFilterSettingsChanged(); @@ -117,14 +144,11 @@ private slots: void treeViewAddressesSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void treeViewAddressesLayoutChanged(); void scrollChanged(); - void onWalletsSynchronized(); private: std::unique_ptr ui_; std::shared_ptr logger_; - std::shared_ptr walletsManager_; - std::shared_ptr signingContainer_; std::shared_ptr appSettings_; std::shared_ptr connectionManager_; std::shared_ptr assetManager_; @@ -133,19 +157,25 @@ private slots: WalletsViewModel * walletsModel_; AddressListModel * addressModel_; AddressSortFilterModel * addressSortFilterModel_; + RootWalletPropertiesDialog * rootDlg_{ nullptr }; + NewAddressDialog* newAddrDlg_{ nullptr }; QAction * actCopyAddr_ = nullptr; QAction * actEditComment_ = nullptr; - QAction * actRevokeSettl_ = nullptr; //QAction * actDeleteWallet_ = nullptr; bs::Address curAddress_; - std::shared_ptr curWallet_; + std::set wallets_; + std::unordered_map walletDetails_; + std::unordered_map walletBalances_; + std::string curWalletId_; + std::string curComment_; unsigned int revokeReqId_ = 0; QString username_; - std::vector> prevSelectedWallets_; + std::vector prevSelectedWallets_; int prevSelectedWalletRow_{-1}; int prevSelectedAddressRow_{-1}; QPoint walletsScrollPos_; QPoint addressesScrollPos_; + std::unordered_map addrDetDialogs_; }; #endif // __WALLETS_WIDGET_H__ diff --git a/BlockSettleUILib/OrderListModel.cpp b/BlockSettleUILib/unused/OrderListModel.cpp similarity index 69% rename from BlockSettleUILib/OrderListModel.cpp rename to BlockSettleUILib/unused/OrderListModel.cpp index b406d0a51..120e92574 100644 --- a/BlockSettleUILib/OrderListModel.cpp +++ b/BlockSettleUILib/unused/OrderListModel.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -9,7 +9,6 @@ */ #include "OrderListModel.h" - #include "AssetManager.h" #include "QuoteProvider.h" #include "UiUtils.h" @@ -17,15 +16,57 @@ #include "bs_proxy_terminal_pb.pb.h" namespace { - const auto kNewOrderColor = QColor{0xFF, 0x7F, 0}; const auto kPendingColor = QColor{0x63, 0xB0, 0xB2}; const auto kRevokedColor = QColor{0xf6, 0xa7, 0x24}; const auto kSettledColor = QColor{0x22, 0xC0, 0x64}; const auto kFailedColor = QColor{0xEC, 0x0A, 0x35}; + bs::network::Order convertOrder(const bs::types::Order &data) { + bs::network::Order order; + + switch (data.status()) { + case bs::types::ORDER_STATUS_PENDING: + order.status = bs::network::Order::Pending; + break; + case bs::types::ORDER_STATUS_FILLED: + order.status = bs::network::Order::Filled; + break; + case bs::types::ORDER_STATUS_VOID: + order.status = bs::network::Order::Failed; + break; + default: + break; + } + + order.assetType = static_cast(data.trade_type()); + + order.exchOrderId = QString::fromStdString(data.id()); + order.side = bs::network::Side::Type(data.side()); + order.pendingStatus = data.status_text(); + order.dateTime = QDateTime::fromMSecsSinceEpoch(data.timestamp_ms()); + order.product = data.product(); + //FIXME: order.quantity = data.quantity(); + order.security = data.product() + "/" + data.product_against(); + //FIXME: order.price = data.price(); + + return order; + } } // namespace +using namespace Blocksettle::Communication; + + +double getOrderValue(const bs::network::Order& order) +{ + double value = - order.quantity * order.price; + if (order.security.substr(0, order.security.find('/')) != order.product) { + value = order.quantity / order.price; + } + + return value; +} + QString OrderListModel::Header::toString(OrderListModel::Header::Index h) { switch (h) { @@ -40,21 +81,11 @@ QString OrderListModel::Header::toString(OrderListModel::Header::Index h) } } -QString OrderListModel::StatusGroup::toString(OrderListModel::StatusGroup::Type sg) -{ - switch (sg) { - case StatusGroup::UnSettled: return tr("UnSettled"); - case StatusGroup::Settled: return tr("Settled"); - default: return tr("Unknown"); - } -} - -OrderListModel::OrderListModel(const std::shared_ptr& assetManager, QObject* parent) +OrderListModel::OrderListModel(QObject* parent) : QAbstractItemModel(parent) - , assetManager_(assetManager) { - reset(); + //reset(); } int OrderListModel::columnCount(const QModelIndex &) const @@ -105,7 +136,7 @@ QVariant OrderListModel::data(const QModelIndex &index, int role) const return static_cast(Qt::AlignRight | Qt::AlignVCenter); default : - return QVariant(); + return {}; } } @@ -113,12 +144,12 @@ QVariant OrderListModel::data(const QModelIndex &index, int role) const if (index.column() == Header::Status) { return d->statusColor_; } else { - return QVariant(); + return {}; } } default : - return QVariant(); + return {}; } } @@ -127,61 +158,54 @@ QVariant OrderListModel::data(const QModelIndex &index, int role) const switch (role) { case Qt::DisplayRole : { - if (index.column() == 0) { + switch(index.column()) { + case Header::NameColumn: return g->security_; - } else { - return QVariant(); + case Header::Quantity: + return g->getQuantity(); + case Header::Value: + return g->getValue(); + case Header::Price: + return g->getPrice(); + case Header::Status: + return g->getStatus(); + default: + return {}; } } - default : - return QVariant(); - } - } - - case DataType::Market : { - auto g = static_cast(idx->data_); - - switch (role) { - case Qt::DisplayRole : { - if (index.column() == 0) { - return g->name_; - } else { - return QVariant(); + case Qt::TextAlignmentRole : { + switch (index.column()) { + case Header::Quantity : + case Header::Price : + case Header::Value : + return static_cast(Qt::AlignRight | Qt::AlignVCenter); + case Header::Status: + return static_cast(Qt::AlignLeft | Qt::AlignVCenter); + default : + return {}; } } - case Qt::FontRole : { - return g->font_; - } - - default : - return QVariant(); - } - } - - case DataType::StatusGroup : { - auto g = static_cast(idx->data_); - - switch (role) { - case Qt::DisplayRole : { - if (index.column() == 0) { - return g->name_; - } else { - return QVariant(); + case Qt::BackgroundRole: { + switch(index.column()) { + case Header::Quantity: + return g->getQuantityColor(); + case Header::Value: + return g->getValueColor(); + default: + return {}; } } - default : - return QVariant(); + default: return {}; } } - default : - return QVariant(); + default: return {}; } } else { - return QVariant(); + return {}; } } @@ -191,16 +215,6 @@ QModelIndex OrderListModel::index(int row, int column, const QModelIndex &parent auto idx = static_cast(parent.internalPointer()); switch (idx->type_) { - case DataType::Market : { - auto m = static_cast(idx->data_); - - if (row < static_cast(m->rows_.size())) { - return createIndex(row, column, &m->rows_[static_cast(row)]->idx_); - } else { - return QModelIndex(); - } - } - case DataType::Group : { auto g = static_cast(idx->data_); @@ -211,57 +225,17 @@ QModelIndex OrderListModel::index(int row, int column, const QModelIndex &parent } } - case DataType::StatusGroup : { - auto g = static_cast(idx->data_); - - if (row < static_cast(g->rows_.size())) { - return createIndex(row, column, &g->rows_[static_cast(row)]->idx_); - } else { - return QModelIndex(); - } - } - default : return QModelIndex(); } } else { switch (row) { - case StatusGroup::UnSettled : - return createIndex(row, column, &unsettled_->idx_); - - case StatusGroup::Settled : - return createIndex(row, column, &settled_->idx_); - default : return QModelIndex(); } } } -int OrderListModel::findGroup(Market *market, Group *group) const -{ - const auto it = std::find_if(market->rows_.cbegin(), market->rows_.cend(), - [group] (const std::unique_ptr &g) { return (&group->idx_ == &g->idx_); }); - - if (it != market->rows_.cend()) { - return static_cast (std::distance(market->rows_.cbegin(), it)); - } else { - return -1; - } -} - -int OrderListModel::findMarket(StatusGroup *statusGroup, Market *market) const -{ - const auto it = std::find_if(statusGroup->rows_.cbegin(), statusGroup->rows_.cend(), - [market] (const std::unique_ptr &m) { return (&market->idx_ == &m->idx_); }); - - if (it != statusGroup->rows_.cend()) { - return static_cast (std::distance(statusGroup->rows_.cbegin(), it)); - } else { - return -1; - } -} - QModelIndex OrderListModel::parent(const QModelIndex &index) const { if (index.isValid()) { @@ -269,24 +243,10 @@ QModelIndex OrderListModel::parent(const QModelIndex &index) const if (idx->parent_) { switch (idx->parent_->type_) { - case DataType::Market : { - auto m = static_cast(idx->parent_->data_); - - return createIndex(findMarket(static_cast(idx->parent_->parent_->data_), - m), 0, &m->idx_); - } - case DataType::Group : { auto g = static_cast(idx->parent_->data_); - return createIndex(findGroup(static_cast(idx->parent_->parent_->data_), - g), 0, &g->idx_); - } - - case DataType::StatusGroup : { - auto g = static_cast(idx->parent_->data_); - - return createIndex(g->row_, 0, &g->idx_); + return createIndex(0 /*FIXME*/, 0, &g->idx_); } default : @@ -303,44 +263,30 @@ QModelIndex OrderListModel::parent(const QModelIndex &index) const int OrderListModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { - // Do not show Settled and UnSettled root items if not connected - return connected_ ? 2 : 0; + return 1; } auto idx = static_cast(parent.internalPointer()); switch (idx->type_) { - case DataType::Market : { - auto m = static_cast(idx->data_); - - return static_cast(m->rows_.size()); - } - case DataType::Group : { auto g = static_cast(idx->data_); return static_cast(g->rows_.size()); } - case DataType::StatusGroup : { - auto g = static_cast(idx->data_); - - return static_cast(g->rows_.size()); - } - - default : - return 0; + default: return 0; } } QVariant OrderListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { - return QVariant(); + return {}; } if (role != Qt::DisplayRole) { - return QVariant(); + return {}; } return Header::toString(static_cast(section)); @@ -348,24 +294,26 @@ QVariant OrderListModel::headerData(int section, Qt::Orientation orientation, in void OrderListModel::onMessageFromPB(const Blocksettle::Communication::ProxyTerminalPb::Response &response) { - connected_ = true; - switch (response.data_case()) { - case Blocksettle::Communication::ProxyTerminalPb::Response::kUpdateOrders: - processUpdateOrders(response.update_orders()); - break; - default: - break; +#if 0 + case Blocksettle::Communication::ProxyTerminalPb::Response::kUpdateOrdersObligations: + processUpdateOrders(response.update_orders_obligations()); + break; + case Blocksettle::Communication::ProxyTerminalPb::Response::kUpdateOrder: + processUpdateOrder(response.update_order()); + break; +#endif + default: + break; } } void OrderListModel::onDisconnected() { - connected_ = false; - - reset(); + //reset(); } +#if 0 void OrderListModel::setOrderStatus(Group *group, int index, const bs::network::Order& order, bool emitUpdate) { @@ -432,21 +380,6 @@ void OrderListModel::setOrderStatus(Group *group, int index, const bs::network:: } } -OrderListModel::StatusGroup::Type OrderListModel::getStatusGroup(const bs::network::Order& order) -{ - switch (order.status) { - case bs::network::Order::New: - case bs::network::Order::Pending: - return StatusGroup::UnSettled; - - case bs::network::Order::Filled: - case bs::network::Order::Failed: - return StatusGroup::Settled; - } - - return StatusGroup::last; -} - std::pair OrderListModel::findItem(const bs::network::Order &order) { const auto itGroups = groups_.find(order.exchOrderId.toStdString()); @@ -503,12 +436,12 @@ std::pair OrderListModel::findItem(const bs::networ } void OrderListModel::removeRowIfContainerChanged(const bs::network::Order &order, - int &oldOrderRow) + int &oldOrderRow, bool force) { // Remove row if container (settled/unsettled) changed. auto git = groups_.find(order.exchOrderId.toStdString()); - if (git != groups_.end() && git->second != getStatusGroup(order) && oldOrderRow >= 0) { + if (git != groups_.end() && (git->second != getStatusGroup(order) || force) && oldOrderRow >= 0) { StatusGroup *tmpsg = (git->second == StatusGroup::UnSettled ? unsettled_.get() : settled_.get()); @@ -590,8 +523,7 @@ void OrderListModel::createGroupsIfNeeded(const bs::network::Order &order, Marke // Create market if it doesn't exist. if (!marketItem) { beginInsertRows(sidx, static_cast(sg->rows_.size()), static_cast(sg->rows_.size())); - sg->rows_.push_back(make_unique( - tr(bs::network::Asset::toString(order.assetType)), &sg->idx_)); + sg->rows_.emplace_back(make_unique(order.assetType, &sg->idx_)); marketItem = sg->rows_.back().get(); endInsertRows(); } @@ -600,8 +532,15 @@ void OrderListModel::createGroupsIfNeeded(const bs::network::Order &order, Marke if (!groupItem) { beginInsertRows(createIndex(findMarket(sg, marketItem), 0, &marketItem->idx_), static_cast(marketItem->rows_.size()), static_cast(marketItem->rows_.size())); - marketItem->rows_.push_back(make_unique( - QString::fromStdString(order.security), &marketItem->idx_)); + + if (bs::network::Asset::isFuturesType(order.assetType)) { + marketItem->rows_.emplace_back(make_unique( + QString::fromStdString(order.security), &marketItem->idx_)); + } else { + marketItem->rows_.emplace_back(make_unique( + QString::fromStdString(order.security), &marketItem->idx_)); + } + groupItem = marketItem->rows_.back().get(); endInsertRows(); } @@ -611,72 +550,66 @@ void OrderListModel::reset() { beginResetModel(); groups_.clear(); - unsettled_ = std::make_unique(StatusGroup::toString(StatusGroup::UnSettled), 0); - settled_ = std::make_unique(StatusGroup::toString(StatusGroup::Settled), 1); + pendingFuturesSettlement_ = std::make_unique(StatusGroup::toString(StatusGroup::PendingSettlements), 0); + unsettled_ = std::make_unique(StatusGroup::toString(StatusGroup::UnSettled), 1); + settled_ = std::make_unique(StatusGroup::toString(StatusGroup::Settled), 2); endResetModel(); } +#endif //0 -void OrderListModel::processUpdateOrders(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrders &message) +void OrderListModel::onOrdersUpdate(const std::vector& orders) { +#if 0 + if (!connected_) { //FIXME: use BS connection event (currently matching one) to set connected_ flag + connected_ = true; + } +#endif // Save latest selected index first - resetLatestChangedStatus(message); + //FIXME: resetLatestChangedStatus(orders); // OrderListModel supposed to work correctly when orders states updated one by one. // We don't use this anymore (server sends all active orders every time) so just clear old caches. // Remove this if old behavior is needed - reset(); - // Use some fake orderId so old code works correctly - int orderId = 0; - - for (const auto &data : message.orders()) { - bs::network::Order order; + //reset(); - switch (data.status()) { - case bs::types::ORDER_STATUS_PENDING: - order.status = bs::network::Order::Pending; - break; - case bs::types::ORDER_STATUS_FILLED: - order.status = bs::network::Order::Filled; - break; - case bs::types::ORDER_STATUS_VOID: - order.status = bs::network::Order::Failed; - break; - default: - break; - } + for (const auto& order : orders) { + //onOrderUpdated(order); + } +} - const bool isXBT = (data.product() == "XBT") || (data.product_against() == "XBT"); - const bool isCC = (assetManager_->getCCLotSize(data.product())) > 0 - || (assetManager_->getCCLotSize(data.product_against()) > 0); +#if 0 +void OrderListModel::processUpdateOrders(const ProxyTerminalPb::Response_UpdateOrdersAndObligations&) +{ + reset(); +} - if (isCC) { - order.assetType = bs::network::Asset::PrivateMarket; - } else if (isXBT) { - order.assetType = bs::network::Asset::SpotXBT; - } else { - order.assetType = bs::network::Asset::SpotFX; +void OrderListModel::processUpdateOrder(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrder &msg) +{ + auto order = convertOrder(msg.order()); + switch (msg.action()) { + case bs::types::ACTION_CREATED: + case bs::types::ACTION_UPDATED: { + onOrderUpdated(order); + break; } + case bs::types::ACTION_REMOVED: { + auto found = findItem(order); + removeRowIfContainerChanged(order, found.second, true); + break; + } + default: + break; - orderId += 1; - order.exchOrderId = QString::number(orderId); - order.side = bs::network::Side::Type(data.side()); - order.pendingStatus = data.status_text(); - order.dateTime = QDateTime::fromMSecsSinceEpoch(data.timestamp_ms()); - order.product = data.product(); - order.quantity = data.quantity(); - order.security = data.product() + "/" + data.product_against(); - order.price = data.price(); - - onOrderUpdated(order); } + //onOrdersUpdate(orders); } -void OrderListModel::resetLatestChangedStatus(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrders &message) +void OrderListModel::resetLatestChangedStatus(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrdersAndObligations &message) { latestChangedTimestamp_ = {}; std::vector> newOrderStatuses(message.orders_size()); for (const auto &data : message.orders()) { - newOrderStatuses.push_back({ data.timestamp_ms(), static_cast(data.status()) }); + newOrderStatuses.emplace_back(decltype(newOrderStatuses)::value_type{ data.timestamp_ms(), static_cast(data.status()) }); } std::sort(newOrderStatuses.begin(), newOrderStatuses.end(), [&](const auto &left, const auto &right) { return left.first < right.first; @@ -696,7 +629,6 @@ void OrderListModel::resetLatestChangedStatus(const Blocksettle::Communication:: } } } - sortedPeviousOrderStatuses_ = std::move(newOrderStatuses); } @@ -707,7 +639,7 @@ void OrderListModel::onOrderUpdated(const bs::network::Order& order) Group *groupItem = nullptr; Market *marketItem = nullptr; - removeRowIfContainerChanged(order, found.second); + removeRowIfContainerChanged(order, found.second, false); findMarketAndGroup(order, marketItem, groupItem); @@ -720,23 +652,7 @@ void OrderListModel::onOrderUpdated(const bs::network::Order& order) if (found.second < 0) { beginInsertRows(parentIndex, 0, 0); - // As quantity is now could be negative need to invert value - double value = - order.quantity * order.price; - if (order.security.substr(0, order.security.find('/')) != order.product) { - value = order.quantity / order.price; - } - - groupItem->rows_.push_front(make_unique( - UiUtils::displayTimeMs(order.dateTime), - QString::fromStdString(order.product), - tr(bs::network::Side::toString(order.side)), - UiUtils::displayQty(order.quantity, order.security, order.product, order.assetType), - UiUtils::displayPriceForAssetType(order.price, order.assetType), - UiUtils::displayValue(value, order.security, order.product, order.assetType), - QString(), - order.exchOrderId, - &groupItem->idx_)); - + groupItem->addOrder(order); setOrderStatus(groupItem, 0, order); endInsertRows(); @@ -745,3 +661,56 @@ void OrderListModel::onOrderUpdated(const bs::network::Order& order) setOrderStatus(groupItem, found.second, order, true); } } +#endif //0 + +void OrderListModel::Group::addOrder(const bs::network::Order& order) +{ + addRow(order); +} + +QVariant OrderListModel::Group::getQuantity() const +{ + return {}; +} + +QVariant OrderListModel::Group::getQuantityColor() const +{ + return {}; +} + +QVariant OrderListModel::Group::getValue() const +{ + return {}; +} + +QVariant OrderListModel::Group::getValueColor() const +{ + return {}; +} + +QVariant OrderListModel::Group::getPrice() const +{ + return {}; +} + +QVariant OrderListModel::Group::getStatus() const +{ + return {}; +} + +void OrderListModel::Group::addRow(const bs::network::Order& order) +{ + // As quantity is now could be negative need to invert value + double value = getOrderValue(order); + + rows_.push_front(make_unique( + UiUtils::displayTimeMs(order.dateTime), + QString::fromStdString(order.product), + tr(bs::network::Side::toString(order.side)), + UiUtils::displayQty(order.quantity, order.security, order.product, order.assetType), + UiUtils::displayPriceForAssetType(order.price, order.assetType), + UiUtils::displayValue(value, order.security, order.product, order.assetType), + QString(), + order.exchOrderId, + &idx_)); +} diff --git a/BlockSettleUILib/OrderListModel.h b/BlockSettleUILib/unused/OrderListModel.h similarity index 55% rename from BlockSettleUILib/OrderListModel.h rename to BlockSettleUILib/unused/OrderListModel.h index 03592aa37..35e6137be 100644 --- a/BlockSettleUILib/OrderListModel.h +++ b/BlockSettleUILib/unused/OrderListModel.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -12,11 +12,11 @@ #define ORDERLISTMODEL_H #include -#include #include +#include +#include #include "CommonTypes.h" - #include #include #include @@ -25,7 +25,7 @@ namespace Blocksettle { namespace Communication { namespace ProxyTerminalPb { class Response; - class Response_UpdateOrders; + class Response_UpdateOrder; } } } @@ -40,6 +40,7 @@ class OrderListModel : public QAbstractItemModel struct Header { enum Index { Time = 0, + NameColumn = 0, Product, Side, Quantity, @@ -51,7 +52,7 @@ class OrderListModel : public QAbstractItemModel static QString toString(Index); }; - OrderListModel(const std::shared_ptr &, QObject *parent = nullptr); + OrderListModel(QObject* parent = nullptr); ~OrderListModel() noexcept override = default; int columnCount(const QModelIndex &parent = QModelIndex()) const override; @@ -61,6 +62,9 @@ class OrderListModel : public QAbstractItemModel int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + //void onOrdersUpdate(const std::vector &); + public slots: void onMessageFromPB(const Blocksettle::Communication::ProxyTerminalPb::Response &response); void onDisconnected(); @@ -72,9 +76,7 @@ public slots: private: enum class DataType { Data = 0, - Group, - Market, - StatusGroup + Group }; struct IndexHelper { @@ -86,8 +88,7 @@ public slots: : parent_(parent) , data_(data) , type_(type) - { - } + {} }; struct Data { @@ -120,81 +121,33 @@ public slots: } }; - struct Group { - std::deque> rows_; - QString security_; - IndexHelper idx_; + struct Group + { + std::deque> rows_; + QString security_; + IndexHelper idx_; Group(const QString &sec, IndexHelper *parent) : security_(sec) , idx_(parent, this, DataType::Group) { } - }; - struct Market { - std::vector> rows_; - QString name_; - IndexHelper idx_; - QFont font_; + virtual ~Group() = default; - Market(const QString &name, IndexHelper *parent) - : name_(name) - , idx_(parent, this, DataType::Market) - { - font_.setBold(true); - } - }; + virtual void addOrder(const bs::network::Order& order); - struct StatusGroup { - std::vector> rows_; - QString name_; - IndexHelper idx_; - int row_; + virtual QVariant getQuantity() const; + virtual QVariant getQuantityColor() const; - enum Type { - first = 0, - UnSettled = first, - Settled, - last - }; + virtual QVariant getValue() const; + virtual QVariant getValueColor() const; + virtual QVariant getPrice() const; - StatusGroup(const QString &name, int row) - : name_(name) - , idx_(nullptr, this, DataType::StatusGroup) - , row_(row) - { - } + virtual QVariant getStatus() const; - static QString toString(Type); + protected: + void addRow(const bs::network::Order& order); }; - static StatusGroup::Type getStatusGroup(const bs::network::Order &); - - void onOrderUpdated(const bs::network::Order &); - int findGroup(Market *market, Group *group) const; - int findMarket(StatusGroup *statusGroup, Market *market) const; - std::pair findItem(const bs::network::Order &order); - void setOrderStatus(Group *group, int index, const bs::network::Order& order, - bool emitUpdate = false); - void removeRowIfContainerChanged(const bs::network::Order &order, int &oldOrderRow); - void findMarketAndGroup(const bs::network::Order &order, Market *&market, Group *&group); - void createGroupsIfNeeded(const bs::network::Order &order, Market *&market, Group *&group); - - void reset(); - void processUpdateOrders(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrders &msg); - void resetLatestChangedStatus(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrders &message); - - std::shared_ptr assetManager_; - std::unordered_map groups_; - std::unique_ptr unsettled_; - std::unique_ptr settled_; - QDateTime latestOrderTimestamp_; - - std::vector> sortedPeviousOrderStatuses_{}; - QDateTime latestChangedTimestamp_; - - bool connected_{}; -}; - #endif // ORDERLISTMODEL_H diff --git a/BlockSettleUILib/OrdersView.cpp b/BlockSettleUILib/unused/OrdersView.cpp similarity index 89% rename from BlockSettleUILib/OrdersView.cpp rename to BlockSettleUILib/unused/OrdersView.cpp index 54e078b4a..8b978612d 100644 --- a/BlockSettleUILib/OrdersView.cpp +++ b/BlockSettleUILib/unused/OrdersView.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2018 - 2020, BlockSettle AB +* Copyright (C) 2018 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -73,18 +73,22 @@ void OrdersView::onSelectRow(const QPersistentModelIndex &row) scrollTo(row, QAbstractItemView::EnsureVisible); } -void OrdersView::onRowsInserted(const QModelIndex &parent, int first, int) +void OrdersView::onRowsInserted(const QModelIndex &parent, int first, int last) { if (!parent.isValid()) { return; } - if (!collapsed_.contains(UiUtils::modelPath(parent, model_))) { - expand(parent); - } - else { - setHasNewItemFlag(parent, true); - } + if (!collapsed_.contains(UiUtils::modelPath(parent, model_))) { + auto topLevelIndex = parent; + while (topLevelIndex.isValid()) { + expand(topLevelIndex); + topLevelIndex = topLevelIndex.parent(); + } + } + else { + setHasNewItemFlag(parent, true); + } if (selectionModel()->hasSelection()) { scrollTo(selectionModel()->selectedIndexes().at(0), QAbstractItemView::EnsureVisible); diff --git a/BlockSettleUILib/OrdersView.h b/BlockSettleUILib/unused/OrdersView.h similarity index 100% rename from BlockSettleUILib/OrdersView.h rename to BlockSettleUILib/unused/OrdersView.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ef28f934..fc5e14c8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # # # *********************************************************************************** -# * Copyright (C) 2011 - 2020, BlockSettle AB +# * Copyright (C) 2018 - 2021, BlockSettle AB # * Distributed under the GNU Affero General Public License (AGPL v3) # * See LICENSE or http://www.gnu.org/licenses/agpl.html # * @@ -10,7 +10,7 @@ # CMAKE_MINIMUM_REQUIRED( VERSION 3.3 ) -SET(CMAKE_CXX_STANDARD 14) +SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_CXX_EXTENSIONS OFF) SET(QT_USE_QTDBUS ON) @@ -23,9 +23,12 @@ IF (NOT CMAKE_BUILD_TYPE) endif() option(BSTERMINAL_SHARED_LIBS "Build shared libraries" OFF) +option(BUILD_WALLETS "Build wallets code" ON) +option(BUILD_TEST_TOOLS "Build test tools" ON) add_definitions(-DSTATIC_BUILD) add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG) +add_definitions(-DBUILD_WALLETS) IF(CMAKE_BUILD_TYPE STREQUAL "Debug") IF(BSTERMINAL_SHARED_LIBS) @@ -161,6 +164,7 @@ FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5Network REQUIRED) FIND_PACKAGE(Qt5PrintSupport REQUIRED) FIND_PACKAGE(Qt5Qml REQUIRED) +FIND_PACKAGE(Qt5QmlWorkerScript REQUIRED) FIND_PACKAGE(Qt5Quick REQUIRED) FIND_PACKAGE(Qt5QuickControls2 REQUIRED) FIND_PACKAGE(Qt5Charts REQUIRED) @@ -201,43 +205,6 @@ IF(NOT WIN32) ADD_DEFINITIONS(-Wno-multichar -Wextra -Wall -Wformat=2) ENDIF(NOT WIN32) -#setup zeromq -SET(ZEROMQ_ROOT ${THIRD_PARTY_COMMON_DIR}/ZeroMQ) -SET(ZEROMQ_INCLUDE_DIR ${ZEROMQ_ROOT}/include) -SET(ZEROMQ_LIB_DIR ${ZEROMQ_ROOT}/lib) - -INCLUDE_DIRECTORIES( ${ZEROMQ_INCLUDE_DIR} ) - -SET(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} ${ZEROMQ_ROOT}/lib) -IF( WIN32 ) - IF(CMAKE_BUILD_TYPE STREQUAL "Debug") - SET( ZMQ_LIB_NAME "libzmq-v142-mt-gd-4_3_3" ) - ELSE() - SET( ZMQ_LIB_NAME "libzmq-v142-mt-4_3_3" ) - ENDIF(CMAKE_BUILD_TYPE STREQUAL "Debug") -ELSE () - IF(BSTERMINAL_SHARED_LIBS) - SET( ZMQ_LIB_NAME "libzmq.so" "zmq" ) - ELSE() - SET( ZMQ_LIB_NAME "libzmq.a" "zmq" ) - ENDIF() -ENDIF( WIN32 ) -FIND_LIBRARY(ZMQ_LIB NAMES ${ZMQ_LIB_NAME} NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) - -IF( NOT ZMQ_LIB ) - IF( WIN32 ) - IF(CMAKE_BUILD_TYPE STREQUAL "Debug") - SET( ZMQ_LIB_NAME "libzmq-v141-mt-gd-4_3_3" ) - ELSE() - SET( ZMQ_LIB_NAME "libzmq-v141-mt-4_3_3" ) - ENDIF(CMAKE_BUILD_TYPE STREQUAL "Debug") - ENDIF( WIN32 ) - FIND_LIBRARY(ZMQ_LIB NAMES ${ZMQ_LIB_NAME} NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH) - IF( NOT ZMQ_LIB) - MESSAGE(FATAL_ERROR "Could not find ZMQ lib") - ENDIF( NOT ZMQ_LIB) -ENDIF( NOT ZMQ_LIB) - # OpenSSL libs SET(OPENSSL_ROOT_DIR ${THIRD_PARTY_COMMON_DIR}/OpenSSL) SET(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} ${OPENSSL_ROOT_DIR}/lib) @@ -335,15 +302,19 @@ INCLUDE_DIRECTORIES( ${QRENCODE_INCLUDE_DIR} ) SET(SPDLOG_INCLUDE_DIR ${THIRD_PARTY_COMMON_DIR}/SPDLog/include) INCLUDE_DIRECTORIES( ${SPDLOG_INCLUDE_DIR} ) +# add JSON +SET(JSON_INCLUDE_DIR ${THIRD_PARTY_COMMON_DIR}/JSON/single_include) +INCLUDE_DIRECTORIES( ${JSON_INCLUDE_DIR} ) + # setup libbtc SET(LIBBTC_PACKAGE_ROOT ${THIRD_PARTY_COMMON_DIR}/libbtc) SET(LIBBTC_LIB_DIR ${LIBBTC_PACKAGE_ROOT}/lib) SET(LIBBTC_INCLUDE_DIR ${LIBBTC_PACKAGE_ROOT}/include) IF (WIN32) - SET(LIBBTC_LIB_NAME libbtc) + SET(LIBBTC_LIB_NAME btc) ELSE(WIN32) - SET(LIBBTC_LIB_NAME liblibbtc.a) + SET(LIBBTC_LIB_NAME libbtc.a) ENDIF(WIN32) FIND_LIBRARY( LIBBTC_LIB NAMES ${LIBBTC_LIB_NAME} PATHS ${LIBBTC_LIB_DIR} NO_DEFAULT_PATH ) @@ -412,20 +383,16 @@ ELSE ( UNIX ) SET( PROTO_LIB ${PROTOBUF_LIBRARIES} ) ENDIF ( UNIX ) -# autheid helper sources -INCLUDE_DIRECTORIES( ${TERMINAL_GUI_ROOT}/AuthAPI/utils/cpp ) -SET( AUTHEID_UTILS ${TERMINAL_GUI_ROOT}/AuthAPI/utils/cpp/autheid_utils.cpp ) - - SET( PROTO_ROOT_DIR ${TERMINAL_GUI_ROOT} ) SET( PATH_TO_GENERATED ${TERMINAL_GUI_ROOT}/generated_proto ) FILE( MAKE_DIRECTORY ${PATH_TO_GENERATED} ) -SET( AUTH_PROTO_LIB_NAME AuthAPI ) SET( BS_PROTO_LIB_NAME BsProtoLib ) -SET( CELER_PROTO_LIB_NAME CelerProtoLib ) SET( BLOCKSETTLE_APP_NAME blocksettle ) SET( SIGNER_APP_NAME blocksettle_signer ) +SET( TERMINAL_CORE_NAME TerminalCore ) +SET( TERMINAL_GUI_QT_NAME TerminalGUI_Qt ) +SET( TERMINAL_GUI_QTQUICK_NAME TerminalGUI_QtQuick ) SET( BLOCKSETTLE_UI_LIBRARY_NAME bsuilib ) SET( BLOCKSETTLE_HW_LIBRARY_NAME HWIntegrations ) SET( CRYPTO_LIB_NAME ArmoryCryptoLib ) @@ -442,6 +409,7 @@ SET( BS_NETWORK_INCLUDE_DIR ${TERMINAL_GUI_ROOT}/common/BlocksettleNetworkingLib SET( COMMON_UI_LIB_INCLUDE_DIR ${TERMINAL_GUI_ROOT}/CommonUI ) SET( COMMON_LIB_INCLUDE_DIR ${TERMINAL_GUI_ROOT}/common/CommonLib ) SET( BS_HW_LIB_INCLUDE_DIR ${TERMINAL_GUI_ROOT}/BlockSettleHW ) +SET( BIP39_INCLUDE_DIR ${TERMINAL_GUI_ROOT}/common/WalletsLib/bip39 ) SET(MDB_DIR ${CRYPTO_LIB_DIR}/lmdb/libraries/liblmdb) @@ -812,11 +780,6 @@ IF(BSTERMINAL_SHARED_LIBS) COMMAND ${QT5_WINDEPLOYQT_EXECUTABLE} --no-opengl-sw --compiler-runtime --no-angle --qmldir ${CMAKE_SOURCE_DIR} ${EXECUTABLE_OUTPUT_PATH}/${CMAKE_BUILD_TYPE}) add_dependencies(${COPY_SHARED_LIBS_NAME} ${SIGNER_APP_NAME} ${BLOCKSETTLE_APP_NAME}) - # libzmq - STRING(REPLACE ".lib" ".dll" ZMQ_LIB_DLL ${ZMQ_LIB}) - add_custom_command(TARGET ${COPY_SHARED_LIBS_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $<$:${ZMQ_LIB_DLL}> $<$>:${ZMQ_LIB_DLL}> ${LIBRARY_OUTPUT_PATH}/${CMAKE_BUILD_TYPE}) - # botan STRING(REPLACE ".lib" ".dll" BOTAN_LIB_DLL ${BOTAN_LIB}) add_custom_command(TARGET ${COPY_SHARED_LIBS_NAME} POST_BUILD @@ -858,29 +821,24 @@ ELSE() # Static version IF(WIN32) set(COPY_SHARED_LIBS_NAME CopySharedLibs) add_custom_target(${COPY_SHARED_LIBS_NAME} ALL) - - # libzmq - STRING(REPLACE ".lib" ".dll" ZMQ_LIB_DLL ${ZMQ_LIB}) - add_custom_command(TARGET ${COPY_SHARED_LIBS_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different $<$:${ZMQ_LIB_DLL}> $<$>:${ZMQ_LIB_DLL}> ${LIBRARY_OUTPUT_PATH}/${CMAKE_BUILD_TYPE}) ENDIF() ENDIF() ADD_SUBDIRECTORY( common/Blocksettle_proto ) -ADD_SUBDIRECTORY( Celer ) -ADD_SUBDIRECTORY( AuthAPI ) +ADD_SUBDIRECTORY(Core) +ADD_SUBDIRECTORY(GUI) ADD_SUBDIRECTORY(BlockSettleUILib) ADD_SUBDIRECTORY(common/BlocksettleNetworkingLib) ADD_SUBDIRECTORY(common/WalletsLib) ADD_SUBDIRECTORY(common/cppForSwig) ADD_SUBDIRECTORY(common/CommonLib) ADD_SUBDIRECTORY(CommonUI) -ADD_SUBDIRECTORY(BlockSettleHW) +#ADD_SUBDIRECTORY(BlockSettleHW) ADD_SUBDIRECTORY(BlockSettleApp) -ADD_SUBDIRECTORY(BlockSettleSigner) +#ADD_SUBDIRECTORY(BlockSettleSigner) IF(BUILD_TESTS) ADD_SUBDIRECTORY(UnitTests) diff --git a/Celer b/Celer deleted file mode 160000 index d94cdcdea..000000000 --- a/Celer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d94cdcdea94653ad581e4419fc74172dd0ab3b2f diff --git a/CommonUI/CMakeLists.txt b/CommonUI/CMakeLists.txt index fa00e6200..8c3098f00 100644 --- a/CommonUI/CMakeLists.txt +++ b/CommonUI/CMakeLists.txt @@ -1,7 +1,7 @@ # # # *********************************************************************************** -# * Copyright (C) 2018 - 2020, BlockSettle AB +# * Copyright (C) 2018 - 2021, BlockSettle AB # * Distributed under the GNU Affero General Public License (AGPL v3) # * See LICENSE or http://www.gnu.org/licenses/agpl.html # * @@ -10,7 +10,7 @@ # CMAKE_MINIMUM_REQUIRED( VERSION 3.3 ) -SET(CMAKE_CXX_STANDARD 14) +SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_CXX_EXTENSIONS OFF) diff --git a/CommonUI/SslCaBundle.cpp b/CommonUI/SslCaBundle.cpp index 8ab82915b..7a3f52590 100644 --- a/CommonUI/SslCaBundle.cpp +++ b/CommonUI/SslCaBundle.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/CommonUI/SslCaBundle.h b/CommonUI/SslCaBundle.h index 384f269a6..030f53a1b 100644 --- a/CommonUI/SslCaBundle.h +++ b/CommonUI/SslCaBundle.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * diff --git a/Core/ApiAdapter.cpp b/Core/ApiAdapter.cpp new file mode 100644 index 000000000..5a8f9e04d --- /dev/null +++ b/Core/ApiAdapter.cpp @@ -0,0 +1,222 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "ApiAdapter.h" +#include + + +class ApiRouter : public bs::message::Router +{ +public: + ApiRouter(const std::shared_ptr &logger) + : bs::message::Router(logger) + {} + +protected: + bool isDefaultRouted(const bs::message::Envelope &env) const override + { + if (std::dynamic_pointer_cast(env.receiver)) { + return true; + } + return bs::message::Router::isDefaultRouted(env); + } +}; + + +class ApiBus : public bs::message::Bus +{ +public: + ApiBus(const std::shared_ptr &logger) + { + queue_ = std::make_shared( + std::make_shared(logger), logger, "API"); + } + + ~ApiBus() override + { + queue_->terminate(); + } + + void addAdapter(const std::shared_ptr &adapter) override + { + queue_->bindAdapter(adapter); + adapter->setQueue(queue_); + adapters_.push_back(adapter); + } + +private: + std::shared_ptr queue_; + std::vector> adapters_; +}; + + +class ApiBusGateway : public ApiBusAdapter +{ +public: + ApiBusGateway(const std::shared_ptr &logger + , ApiAdapter *parent) + : logger_(logger), parent_(parent) + , user_(std::make_shared(0)) + , userTermBroadcast_(std::make_shared(bs::message::TerminalUsers::BROADCAST)) + {} + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "APIbusGW"; } + + bool processBroadcast(const bs::message::Envelope& env) override + { + return false; // ignore unsolicited broadcasts + } + + bool process(const bs::message::Envelope &env) override + { + auto envCopy = env; + envCopy.setId(0); + envCopy.sender = parent_->user_; + + if (std::dynamic_pointer_cast(env.receiver)) { + if (parent_->pushFill(envCopy)) { + if (env.isRequest()) { + std::unique_lock lock(mtxIdMap_); + idMap_[envCopy.foreignId()] = { env.foreignId(), env.sender }; + } + return true; + } + else { + return false; + } + } + else if (env.receiver->isFallback()) { + if (env.isRequest()) { + envCopy.receiver = userTermBroadcast_; + return parent_->pushFill(envCopy); + } + else { + std::unique_lock lock(mtxIdMap_); + const auto &itId = idMap_.find(env.responseId()); + if (itId == idMap_.end()) { + envCopy.receiver = userTermBroadcast_; + return parent_->pushFill(envCopy); + } + else { + envCopy = bs::message::Envelope::makeResponse(parent_->user_ + , itId->second.requester, env.message, itId->second.id); + envCopy.setForeignId(env.foreignId()); + envCopy.executeAt = env.executeAt; + if (parent_->pushFill(envCopy)) { + idMap_.erase(env.responseId()); + } + return true; + } + } + } + return true; + } + + bool pushToApiBus(const bs::message::Envelope &env) + { + auto envCopy = env; + envCopy.setId(0); + envCopy.receiver.reset(); + if (!env.isRequest() && env.receiver) { + std::unique_lock lock(mtxIdMap_); + const auto& itIdMap = idMap_.find(env.responseId()); + if (itIdMap != idMap_.end()) { + envCopy = bs::message::Envelope::makeResponse(env.sender + , itIdMap->second.requester, env.message, itIdMap->second.id); + envCopy.setForeignId(env.foreignId()); + envCopy.executeAt = env.executeAt; + + if (pushFill(envCopy)) { + idMap_.erase(itIdMap); + return true; + } + return false; + } + } + bool rc = pushFill(envCopy); + if (rc && env.isRequest()) { + std::unique_lock lock(mtxIdMap_); + idMap_[envCopy.foreignId()] = { env.foreignId(), env.sender }; + } + return rc; + } + +private: + std::shared_ptr logger_; + ApiAdapter * parent_{ nullptr }; + std::shared_ptr user_; + std::shared_ptr userTermBroadcast_; + + struct RequestData { + uint64_t id; + std::shared_ptr requester; + }; + std::mutex mtxIdMap_; + std::map idMap_; +}; + + +ApiAdapter::ApiAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(bs::message::TerminalUsers::API)) +{ + fallbackUser_ = std::make_shared(bs::message::TerminalUsers::Unknown); // RelayAdapter member + apiBus_ = std::make_shared(logger); + gwAdapter_ = std::make_shared(logger, this); + apiBus_->addAdapter(gwAdapter_); +} + +void ApiAdapter::run(int &argc, char **argv) +{ + if (mainLoopAdapter_) { + mainLoopAdapter_->run(argc, argv); + } + else { + while (true) { + std::this_thread::sleep_for(std::chrono::milliseconds{ 10 }); + } + } +} + +void ApiAdapter::add(const std::shared_ptr &adapter) +{ + const auto &runner = std::dynamic_pointer_cast(adapter); + if (runner) { + if (mainLoopAdapter_) { + logger_->error("[{}] main loop adapter already set - ignoring {}" + , __func__, adapter->name()); + } + else { + mainLoopAdapter_ = runner; + } + } + const auto userId = ++nextApiUser_; + logger_->debug("[{}] {} has id {}", __func__, adapter->name(), userId); + adapter->setUserId(userId); + apiBus_->addAdapter(adapter); +} + +bool ApiAdapter::process(const bs::message::Envelope &env) +{ + RelayAdapter::process(env); + if (env.receiver->value() == user_->value()) { + return gwAdapter_->pushToApiBus(env); + } + return true; +} + +bool ApiAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (RelayAdapter::processBroadcast(env)) { + return gwAdapter_->pushToApiBus(env); + } + return false; +} diff --git a/Core/ApiAdapter.h b/Core/ApiAdapter.h new file mode 100644 index 000000000..f95782014 --- /dev/null +++ b/Core/ApiAdapter.h @@ -0,0 +1,106 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef API_ADAPTER_H +#define API_ADAPTER_H + +#include "Message/Adapter.h" +#include "TerminalMessage.h" + +namespace bs { + namespace message { + class UserAPI : public User + { + public: + UserAPI(UserValue value) : User(value) {} + + bool isFallback() const override + { + return (value() == 0); + } + + bool isBroadcast() const override + { + return (value() < 0); + } + + std::string name() const override + { + if (value() == 0) { + return "Gateway"; + } + else if (value() < 0) { + return "Broadcast"; + } + else if (!name_.empty()) { + return name_; + } + else { + return "User#" + std::to_string(value()); + } + } + + void setName(const std::string& name) + { + name_ = name; + } + + private: + std::string name_; + }; + } +} +class ApiBus; +class ApiBusGateway; + + +class ApiBusAdapter : public bs::message::Adapter +{ +public: + Users supportedReceivers() const override { return { user_ }; } + + void setUserId(const bs::message::UserValue userVal) + { + user_ = std::make_shared(userVal); + user_->setName(name()); + } + +protected: + std::shared_ptr user_; +}; + + +class ApiAdapter : public bs::message::RelayAdapter, public bs::MainLoopRuner +{ + friend class ApiBusGateway; +public: + ApiAdapter(const std::shared_ptr &); + ~ApiAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope& env) override; + + Users supportedReceivers() const override { return { user_, fallbackUser_ }; } + std::string name() const override { return "API"; } + + void add(const std::shared_ptr &); + void run(int &argc, char **argv) override; + +private: + std::shared_ptr user_; + std::shared_ptr logger_; + std::shared_ptr apiBus_; + std::shared_ptr mainLoopAdapter_; + std::shared_ptr gwAdapter_; + + bs::message::UserValue nextApiUser_{ 0 }; +}; + +#endif // API_ADAPTER_H diff --git a/Core/ApiJson.cpp b/Core/ApiJson.cpp new file mode 100644 index 000000000..97bfd9096 --- /dev/null +++ b/Core/ApiJson.cpp @@ -0,0 +1,670 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "ApiJson.h" +#include +#include "Address.h" +#include "MessageUtils.h" +#include "ProtobufUtils.h" +#include "SslServerConnection.h" +#include "StringUtils.h" + +#include "common.pb.h" +#include "json.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace BlockSettle::API::JSON; +using namespace bs::message; +using namespace ProtobufUtils; + +static constexpr auto kRequestTimeout = std::chrono::seconds{ 90 }; + + +ApiJsonAdapter::ApiJsonAdapter(const std::shared_ptr &logger) + : logger_(logger) + , userSettings_(UserTerminal::create(TerminalUsers::Settings)) +{} + +bool ApiJsonAdapter::process(const Envelope &env) +{ + if (std::dynamic_pointer_cast(env.sender)) { + switch (env.sender->value()) { + case TerminalUsers::Settings: + return processSettings(env); + case TerminalUsers::Blockchain: + return processBlockchain(env); + case TerminalUsers::Signer: + return processSigner(env); + case TerminalUsers::Wallets: + return processWallets(env); + case TerminalUsers::BsServer: + return processBsServer(env); + case TerminalUsers::Settlement: + return processSettlement(env); + case TerminalUsers::Matching: + return processMatching(env); + case TerminalUsers::MktData: + return processMktData(env); + case TerminalUsers::OnChainTracker: + return processOnChainTrack(env); + case TerminalUsers::Assets: + return processAssets(env); + default: break; + } + } + else if (env.receiver && (env.sender->value() == user_->value()) + && (env.sender->value() == env.receiver->value())) { // own to self messages + if (env.message == "GC") { + processGCtimeout(); + } + } + else { + logger_->warn("[{}] non-terminal #{} user {}", __func__, env.foreignId() + , env.sender->name()); + } + return true; +} + +bool ApiJsonAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (std::dynamic_pointer_cast(env.sender)) { + switch (env.sender->value()) { + case TerminalUsers::System: + return processAdminMessage(env); + case TerminalUsers::Blockchain: + return processBlockchain(env); + case TerminalUsers::Signer: + return processSigner(env); + case TerminalUsers::Wallets: + return processWallets(env); + case TerminalUsers::BsServer: + return processBsServer(env); + case TerminalUsers::Settlement: + return processSettlement(env); + case TerminalUsers::Matching: + return processMatching(env); + case TerminalUsers::MktData: + return processMktData(env); + case TerminalUsers::OnChainTracker: + return processOnChainTrack(env); + case TerminalUsers::Assets: + return processAssets(env); + default: break; + } + } + return false; +} + +static std::shared_ptr mapUser(const EnvelopeIn::MessageCase& user) +{ + static const std::map> usersMap = { + { EnvelopeIn::kSigner, UserTerminal::create(TerminalUsers::Signer) }, + { EnvelopeIn::kMatching, UserTerminal::create(TerminalUsers::Matching) }, + { EnvelopeIn::kAssets, UserTerminal::create(TerminalUsers::Assets) }, + { EnvelopeIn::kMarketData, UserTerminal::create(TerminalUsers::MktData) }, + { EnvelopeIn::kMdHist, UserTerminal::create(TerminalUsers::MDHistory) }, + { EnvelopeIn::kBlockchain, UserTerminal::create(TerminalUsers::Blockchain) }, + { EnvelopeIn::kWallets, UserTerminal::create(TerminalUsers::Wallets) }, + { EnvelopeIn::kOnChainTracker, UserTerminal::create(TerminalUsers::OnChainTracker) }, + { EnvelopeIn::kSettlement, UserTerminal::create(TerminalUsers::Settlement) }, + { EnvelopeIn::kChat, UserTerminal::create(TerminalUsers::Chat) }, + { EnvelopeIn::kBsServer, UserTerminal::create(TerminalUsers::BsServer) } + }; + try { + return usersMap.at(user); + } + catch (const std::exception&) { + return {}; + } +} + +void ApiJsonAdapter::OnDataFromClient(const std::string& clientId + , const std::string& data) +{ + const auto& sendErrorReply = [this, clientId] + (const std::string& id, const std::string& errorMsg) + { + EnvelopeOut env; + if (!id.empty()) { + env.set_id(id); + } + auto msg = env.mutable_error(); + if (!errorMsg.empty()) { + msg->set_error_text(errorMsg); + } + const auto& jsonData = toJsonCompact(env); + if (!connection_->SendDataToClient(clientId, jsonData)) { + logger_->error("[ApiJsonAdapter::OnDataFromClient::sendErrorReply] failed to send"); + } + }; + logger_->debug("[{}] received from {}:\n{}", __func__, bs::toHex(clientId), data); + EnvelopeIn jsonMsg; + if (!fromJson(data, &jsonMsg)) { + sendErrorReply({}, "failed to parse"); + return; + } + std::string serMsg; + switch (jsonMsg.message_case()) { + case EnvelopeIn::kSigner: + serMsg = jsonMsg.signer().SerializeAsString(); + break; + case EnvelopeIn::kMatching: + serMsg = jsonMsg.matching().SerializeAsString(); + break; + case EnvelopeIn::kAssets: + serMsg = jsonMsg.assets().SerializeAsString(); + break; + case EnvelopeIn::kMarketData: + serMsg = jsonMsg.market_data().SerializeAsString(); + break; + case EnvelopeIn::kMdHist: + serMsg = jsonMsg.md_hist().SerializeAsString(); + break; + case EnvelopeIn::kBlockchain: + serMsg = jsonMsg.blockchain().SerializeAsString(); + break; + case EnvelopeIn::kWallets: + serMsg = jsonMsg.wallets().SerializeAsString(); + break; + case EnvelopeIn::kOnChainTracker: + serMsg = jsonMsg.on_chain_tracker().SerializeAsString(); + break; + case EnvelopeIn::kSettlement: + serMsg = jsonMsg.settlement().SerializeAsString(); + break; + case EnvelopeIn::kChat: + serMsg = jsonMsg.chat().SerializeAsString(); + break; + case EnvelopeIn::kBsServer: + serMsg = jsonMsg.bs_server().SerializeAsString(); + break; + default: + logger_->warn("[{}] unknown message for {}", __func__, jsonMsg.message_case()); + break; + } + const auto& user = mapUser(jsonMsg.message_case()); + if (!user) { + logger_->error("[{}] failed to map user from {}", __func__ + , jsonMsg.message_case()); + return; + } + const auto msgId = pushRequest(user_, user, serMsg); + if (!msgId) { + sendErrorReply(jsonMsg.id(), "internal error"); + } + requests_[msgId] = { clientId, jsonMsg.id(), std::chrono::system_clock::now() }; +} + +void ApiJsonAdapter::OnClientConnected(const std::string& clientId + , const Details& details) +{ + connectedClients_.insert(clientId); + logger_->info("[{}] {} (total {}) connected from {}", __func__, bs::toHex(clientId) + , connectedClients_.size(), details.at(Detail::IpAddr)); + EnvelopeOut env; + auto msg = env.mutable_connected(); + msg->set_wallets_ready(walletsReady_); + msg->set_logged_user(loggedInUser_); + msg->set_matching_connected(matchingConnected_); + msg->set_blockchain_state(armoryState_); + msg->set_top_block(blockNum_); + msg->set_signer_state(signerState_); + const auto& jsonData = toJsonCompact(env); + if (!connection_->SendDataToClient(clientId, jsonData)) { + logger_->error("[ApiJsonAdapter::OnClientConnected] failed to send"); + } + if (connectedClients_.size() == 1) { + sendGCtimeout(); + } +} + +void ApiJsonAdapter::OnClientDisconnected(const std::string& clientId) +{ + connectedClients_.erase(clientId); + logger_->info("[{}] {} disconnected ({} remain)", __func__, bs::toHex(clientId) + , connectedClients_.size()); +} + +bool ApiJsonAdapter::processSettings(const Envelope &env) +{ + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kGetResponse: + return processSettingsGetResponse(msg.get_response()); + case SettingsMessage::kSettingsUpdated: + return processSettingsGetResponse(msg.settings_updated()); + case SettingsMessage::kApiPrivkey: + connectionPrivKey_.assign(msg.api_privkey().cbegin(), msg.api_privkey().cend()); + connection_ = std::make_unique(logger_ + , SslServerConnectionParams{ true, false, true + , bs::network::ws::generateSelfSignedCert(connectionPrivKey_) + , connectionPrivKey_, [this](const std::string& publicKey) -> bool + { + if (clientPubKeys_.empty()) { // no client keys configured + return true; // TODO: change this if unknown clients are forbidden + } + if (clientPubKeys_.find(publicKey) == clientPubKeys_.end()) { + logger_->error("[ApiJsonAdapter] unknown client key {}" + , bs::toHex(publicKey)); + return false; + } + return true; + } } + ); + break; + case SettingsMessage::kApiClientKeys: + for (const auto& clientKey : msg.api_client_keys().pub_keys()) { + clientPubKeys_.insert(clientKey); + } + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processSettingsGetResponse(const SettingsMessage_SettingsResponse &response) +{ + std::map settings; + for (const auto &setting : response.responses()) { + switch (setting.request().index()) { + case SetIdx_ExtConnPort: + if (!connection_) { + SPDLOG_LOGGER_ERROR(logger_, "connection should be created at this point"); + break; + } + if (connection_->BindConnection("0.0.0.0", setting.s(), this)) { + logger_->debug("[ApiJsonAdapter] connection ready on port {}", setting.s()); + } + else { + SPDLOG_LOGGER_ERROR(logger_, "failed to bind to {}", setting.s()); + } + break; + } + } + return true; +} + +bool ApiJsonAdapter::processAdminMessage(const Envelope &env) +{ + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse admin msg #{}", __func__, env.foreignId()); + return false; + } + switch (msg.data_case()) { + case AdministrativeMessage::kStart: + processStart(); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processBlockchain(const Envelope &env) +{ + ArmoryMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[QtGuiAdapter::processBlockchain] failed to parse msg #{}" + , env.foreignId()); + if (!env.receiver) { + logger_->debug("[{}] no receiver", __func__); + } + return true; + } + switch (msg.data_case()) { + case ArmoryMessage::kStateChanged: + armoryState_ = msg.state_changed().state(); + blockNum_ = msg.state_changed().top_block(); + sendReplyToClient(0, msg, env.sender); + break; + case ArmoryMessage::kNewBlock: + blockNum_ = msg.new_block().top_block(); + sendReplyToClient(0, msg, env.sender); + break; + case ArmoryMessage::kWalletRegistered: + if (msg.wallet_registered().success() && msg.wallet_registered().wallet_id().empty()) { + walletsReady_ = true; + } + break; + case ArmoryMessage::kLedgerEntries: [[fallthrough]]; + case ArmoryMessage::kAddressHistory: [[fallthrough]]; + case ArmoryMessage::kFeeLevelsResponse: + if (hasRequest(env.responseId())) { + sendReplyToClient(env.responseId(), msg, env.sender); + } + break; + case ArmoryMessage::kZcReceived: [[fallthrough]]; + case ArmoryMessage::kZcInvalidated: + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processSigner(const Envelope &env) +{ + SignerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[QtGuiAdapter::processSigner] failed to parse msg #{}" + , env.foreignId()); + if (!env.receiver) { + logger_->debug("[{}] no receiver", __func__); + } + return true; + } + switch (msg.data_case()) { + case SignerMessage::kState: + signerState_ = msg.state().code(); + sendReplyToClient(0, msg, env.sender); + break; + case SignerMessage::kNeedNewWalletPrompt: + break; + case SignerMessage::kSignTxResponse: + sendReplyToClient(env.responseId(), msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processWallets(const Envelope &env) +{ + WalletsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case WalletsMessage::kWalletLoaded: + case WalletsMessage::kHdWallet: + sendReplyToClient(0, msg, env.sender); + break; + case WalletsMessage::kWalletAddresses: + case WalletsMessage::kAddrComments: + case WalletsMessage::kWalletData: + case WalletsMessage::kWalletBalances: + case WalletsMessage::kTxDetailsResponse: + case WalletsMessage::kWalletsListResponse: + case WalletsMessage::kUtxos: + case WalletsMessage::kReservedUtxos: + if (hasRequest(env.responseId())) { + sendReplyToClient(env.responseId(), msg, env.sender); + } + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processOnChainTrack(const Envelope &env) +{ + OnChainTrackMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case OnChainTrackMessage::kAuthState: + case OnChainTrackMessage::kVerifiedAuthAddresses: + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processAssets(const bs::message::Envelope& env) +{ + AssetsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case AssetsMessage::kSubmittedAuthAddrs: + if (hasRequest(env.responseId())) { + sendReplyToClient(env.responseId(), msg, env.sender); + } + break; + case AssetsMessage::kBalance: + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +void ApiJsonAdapter::processStart() +{ + logger_->debug("[ApiJsonAdapter::processStart]"); + SettingsMessage msg; + msg.set_api_privkey(""); + pushRequest(user_, userSettings_, msg.SerializeAsString()); + + msg.mutable_api_client_keys(); + pushRequest(user_, userSettings_, msg.SerializeAsString()); + + auto msgReq = msg.mutable_get_request(); + auto setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_ExtConnPort); + setReq->set_type(SettingType_String); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +bool ApiJsonAdapter::processBsServer(const bs::message::Envelope& env) +{ + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case BsServerMessage::kStartLoginResult: + case BsServerMessage::kOrdersUpdate: + sendReplyToClient(0, msg, env.sender); // multicast to all connected clients + break; + case BsServerMessage::kLoginResult: + loggedInUser_ = msg.login_result().login(); + sendReplyToClient(0, msg, env.sender); // multicast login results to all connected clients + break; + case BsServerMessage::kDisconnected: + loggedInUser_.clear(); + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processSettlement(const bs::message::Envelope& env) +{ + SettlementMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettlementMessage::kQuote: + case SettlementMessage::kMatchedQuote: + case SettlementMessage::kFailedSettlement: + case SettlementMessage::kPendingSettlement: + case SettlementMessage::kSettlementComplete: + case SettlementMessage::kQuoteCancelled: + case SettlementMessage::kQuoteReqNotif: + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processMatching(const bs::message::Envelope& env) +{ + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MatchingMessage::kLoggedIn: + matchingConnected_ = true; + sendReplyToClient(0, msg, env.sender); + break; + case MatchingMessage::kLoggedOut: + matchingConnected_ = false; + loggedInUser_.clear(); + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::processMktData(const bs::message::Envelope& env) +{ + MktDataMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MktDataMessage::kDisconnected: + case MktDataMessage::kNewSecurity: + case MktDataMessage::kAllInstrumentsReceived: + case MktDataMessage::kPriceUpdate: + sendReplyToClient(0, msg, env.sender); + break; + default: break; + } + return true; +} + +bool ApiJsonAdapter::hasRequest(uint64_t msgId) const +{ + return (requests_.find(msgId) != requests_.end()); +} + +bool ApiJsonAdapter::sendReplyToClient(uint64_t msgId + , const google::protobuf::Message& msg + , const std::shared_ptr& sender) +{ + if (!connection_ || connectedClients_.empty()) { + return true; + } + EnvelopeOut envOut; + std::string clientId; + if (msgId) { + const auto& itReq = requests_.find(msgId); + if (itReq == requests_.end()) { + logger_->error("[{}] no request found for #{} from {}; reply:\n{}" + , __func__, msgId, sender->name(), msg.DebugString()); + return false; + } + clientId = itReq->second.clientId; + envOut.set_id(itReq->second.requestId); + itReq->second.replied = true; + } + google::protobuf::Message* targetMsg = nullptr; + switch (sender->value()) { + case TerminalUsers::Signer: + targetMsg = envOut.mutable_signer(); + break; + case TerminalUsers::Matching: + targetMsg = envOut.mutable_matching(); + break; + case TerminalUsers::Assets: + targetMsg = envOut.mutable_assets(); + break; + case TerminalUsers::MktData: + targetMsg = envOut.mutable_market_data(); + break; + case TerminalUsers::MDHistory: + targetMsg = envOut.mutable_md_hist(); + break; + case TerminalUsers::Blockchain: + targetMsg = envOut.mutable_blockchain(); + break; + case TerminalUsers::Wallets: + targetMsg = envOut.mutable_wallets(); + break; + case TerminalUsers::OnChainTracker: + targetMsg = envOut.mutable_on_chain_tracker(); + break; + case TerminalUsers::Settlement: + targetMsg = envOut.mutable_settlement(); + break; + case TerminalUsers::Chat: + targetMsg = envOut.mutable_chat(); + break; + case TerminalUsers::BsServer: + targetMsg = envOut.mutable_bs_server(); + break; + default: + logger_->warn("[{}] unhandled sender {}", __func__, sender->value()); + break; + } + if (targetMsg) { + targetMsg->CopyFrom(msg); + } + else { + logger_->error("[{}] unknown target message", __func__); + return false; + } + const auto& jsonData = toJsonCompact(envOut); + if (clientId.empty()) { + return connection_->SendDataToAllClients(jsonData); + } + else { + if (connectedClients_.find(clientId) == connectedClients_.end()) { + logger_->error("[{}] client {} is not connected", __func__, bs::toHex(clientId)); + return false; + } + return connection_->SendDataToClient(clientId, jsonData); + } +} + +void ApiJsonAdapter::sendGCtimeout() +{ + const auto& timeNow = bs::message::bus_clock::now(); + pushRequest(user_, user_, "GC", timeNow + kRequestTimeout); +} + +void ApiJsonAdapter::processGCtimeout() +{ + std::vector deleteRequests; + const auto& timeNow = std::chrono::system_clock::now(); + for (const auto& req : requests_) { + if ((timeNow - req.second.timestamp) > kRequestTimeout) { + if (!req.second.replied) { + logger_->debug("[{}] request #{}/{} from {} was never replied", __func__ + , req.first, req.second.requestId, bs::toHex(req.second.clientId)); + } + deleteRequests.push_back(req.first); + } + } + if (!deleteRequests.empty()) { + logger_->debug("[{}] removing {} outdated request[s]", __func__ + , deleteRequests.size()); + for (const auto& id : deleteRequests) { + requests_.erase(id); + } + } + if (!connectedClients_.empty()) { + sendGCtimeout(); + } +} diff --git a/Core/ApiJson.h b/Core/ApiJson.h new file mode 100644 index 000000000..f10527c5e --- /dev/null +++ b/Core/ApiJson.h @@ -0,0 +1,96 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef API_JSON_H +#define API_JSON_H + +#include +#include +#include "Address.h" +#include "ApiAdapter.h" +#include "ServerConnection.h" +#include "ServerConnectionListener.h" +#include "Wallets/SignContainer.h" +#include "WsConnection.h" + +namespace BlockSettle { + namespace Terminal { + class SettingsMessage_SettingsResponse; + } +} + +class ApiJsonAdapter : public ApiBusAdapter, public ServerConnectionListener +{ +public: + ApiJsonAdapter(const std::shared_ptr &); + ~ApiJsonAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "JSON API"; } + +protected: // ServerConnectionListener overrides + void OnDataFromClient(const std::string& clientId, const std::string& data) override; + void OnClientConnected(const std::string& clientId, const Details& details) override; + void OnClientDisconnected(const std::string& clientId) override; + +private: + bool processSettings(const bs::message::Envelope &); + bool processSettingsGetResponse(const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + + bool processAdminMessage(const bs::message::Envelope &); + bool processAssets(const bs::message::Envelope&); + bool processBlockchain(const bs::message::Envelope&); + bool processBsServer(const bs::message::Envelope&); + bool processMatching(const bs::message::Envelope&); + bool processMktData(const bs::message::Envelope&); + bool processOnChainTrack(const bs::message::Envelope&); + bool processSettlement(const bs::message::Envelope&); + bool processSigner(const bs::message::Envelope&); + bool processWallets(const bs::message::Envelope&); + + void processStart(); + + bool hasRequest(uint64_t msgId) const; + bool sendReplyToClient(uint64_t msgId, const google::protobuf::Message& + , const std::shared_ptr &sender); + + void sendGCtimeout(); + void processGCtimeout(); + +private: + std::shared_ptr logger_; + std::shared_ptr userSettings_; + std::unique_ptr connection_; + bs::network::ws::PrivateKey connectionPrivKey_; + std::set clientPubKeys_; + std::set connectedClients_; + + std::string loggedInUser_; + bool matchingConnected_{ false }; + int armoryState_{ -1 }; + uint32_t blockNum_{ 0 }; + int signerState_{ -1 }; + bool walletsReady_{ false }; + + struct ClientRequest + { + std::string clientId; + std::string requestId; + std::chrono::system_clock::time_point timestamp; + bool replied{ false }; + }; + std::map requests_; +}; + + +#endif // API_JSON_H diff --git a/Core/AssetsAdapter.cpp b/Core/AssetsAdapter.cpp new file mode 100644 index 000000000..f633376cf --- /dev/null +++ b/Core/AssetsAdapter.cpp @@ -0,0 +1,187 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "AssetsAdapter.h" +#include +#include "TerminalMessage.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace bs::message; +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; + +AssetsAdapter::AssetsAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(bs::message::TerminalUsers::Assets)) +{ + assetMgr_ = std::make_unique(logger, this); +} + +bool AssetsAdapter::process(const bs::message::Envelope &env) +{ + if (env.sender->value() == bs::message::TerminalUsers::Settings) { + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings message #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kGetResponse: + return processGetSettings(msg.get_response()); + default: break; + } + } + else if (env.sender->value() == bs::message::TerminalUsers::Matching) { + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse matching message #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MatchingMessage::kSubmittedAuthAddresses: + return processSubmittedAuth(msg.submitted_auth_addresses()); + default: break; + } + } + else if (env.receiver->value() == user_->value()) { + AssetsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own message #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case AssetsMessage::kSubmitAuthAddress: + return processSubmitAuth(msg.submit_auth_address()); + default: break; + } + } + return true; +} + +bool AssetsAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}" + , __func__, env.foreignId()); + return false; + } + if (msg.data_case() == AdministrativeMessage::kStart) { + SettingsMessage msgSet; + auto msgReq = msgSet.mutable_get_request(); + auto setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_BlockSettleSignAddress); + pushRequest(user_, UserTerminal::create(TerminalUsers::Settings) + , msgSet.SerializeAsString()); + } + } + else if (env.sender->value() == bs::message::TerminalUsers::Matching) { + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse matching message #{}", __func__, env.foreignId()); + return false; + } + switch (msg.data_case()) { + case MatchingMessage::kLoggedIn: + return onMatchingLogin(msg.logged_in()); + default: break; + } + } + else if (env.sender->value() == bs::message::TerminalUsers::BsServer) { + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse BS message #{}", __func__, env.foreignId()); + return false; + } + switch (msg.data_case()) { + case BsServerMessage::kBalanceUpdated: + return processBalance(msg.balance_updated().currency(), msg.balance_updated().value()); + default: break; + } + } + return false; +} + +void AssetsAdapter::onCcPriceChanged(const std::string& currency) +{ +} + +void AssetsAdapter::onXbtPriceChanged(const std::string& currency) +{ +} + +void AssetsAdapter::onFxBalanceLoaded() +{ +} + +void AssetsAdapter::onFxBalanceCleared() +{ +} + +void AssetsAdapter::onBalanceChanged(const std::string& currency) +{ +} + +void AssetsAdapter::onTotalChanged() +{ +} + +void AssetsAdapter::onSecuritiesChanged() +{ +} + +bool AssetsAdapter::processGetSettings(const SettingsMessage_SettingsResponse& response) +{ + for (const auto& setting : response.responses()) { + switch (setting.request().index()) { + default: break; + } + } + return true; +} + +bool AssetsAdapter::onMatchingLogin(const MatchingMessage_LoggedIn&) +{ + MatchingMessage msg; + msg.mutable_get_submitted_auth_addresses(); + return pushRequest(user_, UserTerminal::create(TerminalUsers::Matching) + , msg.SerializeAsString()); +} + +bool AssetsAdapter::processSubmittedAuth(const MatchingMessage_SubmittedAuthAddresses& response) +{ + AssetsMessage msg; + auto msgBC = msg.mutable_submitted_auth_addrs(); + for (const auto& addr : response.addresses()) { + msgBC->add_addresses(addr); + } + return pushBroadcast(user_, msg.SerializeAsString()); +} + +bool AssetsAdapter::processSubmitAuth(const std::string& address) +{ // currently using Celer storage for this, but this might changed at some point + MatchingMessage msg; + msg.set_submit_auth_address(address); + return pushRequest(user_, UserTerminal::create(TerminalUsers::Matching) + , msg.SerializeAsString()); +} + +bool AssetsAdapter::processBalance(const std::string& currency, double value) +{ + AssetsMessage msg; + auto msgBal = msg.mutable_balance(); + msgBal->set_currency(currency); + msgBal->set_value(value); + return pushBroadcast(user_, msg.SerializeAsString()); +} diff --git a/Core/AssetsAdapter.h b/Core/AssetsAdapter.h new file mode 100644 index 000000000..ae88c1905 --- /dev/null +++ b/Core/AssetsAdapter.h @@ -0,0 +1,66 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef ASSETS_ADAPTER_H +#define ASSETS_ADAPTER_H + +#include "Message/Adapter.h" +#include "AssetManager.h" + +namespace spdlog { + class logger; +} +namespace BlockSettle { + namespace Terminal { + class MatchingMessage_LoggedIn; + class MatchingMessage_SubmittedAuthAddresses; + class SettingsMessage_BootstrapData; + class SettingsMessage_SettingsResponse; + } +} + +class AssetsAdapter : public bs::message::Adapter + , public AssetCallbackTarget +{ +public: + AssetsAdapter(const std::shared_ptr &); + ~AssetsAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "Assets"; } + +private: // AssetMgr callbacks override + void onCcPriceChanged(const std::string& currency) override; + void onXbtPriceChanged(const std::string& currency) override; + void onFxBalanceLoaded() override; + void onFxBalanceCleared() override; + + void onBalanceChanged(const std::string& currency) override; + void onTotalChanged() override; + void onSecuritiesChanged() override; + + //internal processing + bool processGetSettings(const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + bool onMatchingLogin(const BlockSettle::Terminal::MatchingMessage_LoggedIn&); + bool processSubmittedAuth(const BlockSettle::Terminal::MatchingMessage_SubmittedAuthAddresses&); + bool processSubmitAuth(const std::string&); + bool processBalance(const std::string& currency, double); + +private: + std::shared_ptr logger_; + std::shared_ptr user_; + std::unique_ptr assetMgr_; +}; + + +#endif // ASSETS_ADAPTER_H diff --git a/Core/BsServerAdapter.cpp b/Core/BsServerAdapter.cpp new file mode 100644 index 000000000..db008164e --- /dev/null +++ b/Core/BsServerAdapter.cpp @@ -0,0 +1,383 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "BsServerAdapter.h" +#include +#include "Bip15xDataConnection.h" +#include "ConnectionManager.h" +#include "PubKeyLoader.h" +#include "RequestReplyCommand.h" +#include "SslCaBundle.h" +#include "TerminalMessage.h" +#include "WsDataConnection.h" + +#include "bs_proxy_terminal_pb.pb.h" +#include "bs_communication.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + + +BsServerAdapter::BsServerAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(TerminalUsers::BsServer)) + , userSettl_(std::make_shared(TerminalUsers::Settlement)) + , userMtch_(std::make_shared(TerminalUsers::Matching)) + , userSettings_(std::make_shared(TerminalUsers::Settings)) + , userWallets_(std::make_shared(TerminalUsers::Wallets)) +{ + connMgr_ = std::make_shared(logger_); + connMgr_->setCaBundle(bs::caBundlePtr(), bs::caBundleSize()); +} + +bool BsServerAdapter::process(const bs::message::Envelope &env) +{ + if (env.sender->value() == TerminalUsers::Settings) { + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings message #{}", __func__ + , env.foreignId()); + return true; + } + if (msg.data_case() == SettingsMessage::kGetResponse) { + return processLocalSettings(msg.get_response()); + } + } + else if (env.receiver->value() == user_->value()) { + return processOwnRequest(env); + } + return true; +} + +bool BsServerAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}" + , __func__, env.foreignId()); + return false; + } + switch (msg.data_case()) { + case AdministrativeMessage::kStart: + case AdministrativeMessage::kRestart: + start(); + return true; + default: break; + } + } + return false; +} + +void BsServerAdapter::start() +{ + SettingsMessage msg; + auto msgReq = msg.mutable_get_request(); + auto req = msgReq->add_requests(); + req->set_source(SettingSource_Local); + req->set_type(SettingType_Int); + req->set_index(SetIdx_Environment); + const auto msgId = pushRequest(user_, UserTerminal::create(TerminalUsers::Settings) + , msg.SerializeAsString()); +} + +bool BsServerAdapter::processOwnRequest(const Envelope &env) +{ + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own request #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case BsServerMessage::kOpenConnection: + return processOpenConnection(); + case BsServerMessage::kCloseConnection: +#if 0 + bsClient_.reset(); +#endif + break; + case BsServerMessage::kStartLogin: + return processStartLogin(msg.start_login()); + case BsServerMessage::kCancelLastLogin: + return processCancelLogin(); + case BsServerMessage::kPubNewKeyResponse: + return processPuBKeyResponse(msg.pub_new_key_response()); + case BsServerMessage::kTimeout: + return processTimeout(msg.timeout()); +#if 0 + case BsServerMessage::kSendMatching: + if (bsClient_) { + bsClient_->celerSend(static_cast(msg.send_matching().message_type()) + , msg.send_matching().data()); + } + break; + case BsServerMessage::kSubmitAuthAddress: + return processSubmitAuthAddr(env, msg.submit_auth_address()); + case BsServerMessage::kSendUnsignedPayin: + return processOutUnsignedPayin(msg.send_unsigned_payin()); + case BsServerMessage::kSendSignedPayin: + return processOutSignedPayin(msg.send_signed_payin()); + case BsServerMessage::kSendSignedPayout: + return processOutSignedPayout(msg.send_signed_payout()); +#endif + default: break; + } + return true; +} + +bool BsServerAdapter::processLocalSettings(const SettingsMessage_SettingsResponse &response) +{ + for (const auto &setting : response.responses()) { + switch (static_cast(setting.request().index())) { + case ApplicationSettings::envConfiguration: + envConfig_ = static_cast(setting.i()); + { + AdministrativeMessage admMsg; + admMsg.set_component_loading(user_->value()); + const auto msgId = pushBroadcast(UserTerminal::create(TerminalUsers::System) + , admMsg.SerializeAsString()); + logger_->debug("[BsServerAdapter::processLocalSettings] #{}", msgId); + } + break; + + default: break; + } + } + return true; +} + +bool BsServerAdapter::processPuBKeyResponse(bool allowed) +{ + if (!futPuBkey_) { + logger_->error("[{}] not waiting for PuB key permission", __func__); + return true; + } + futPuBkey_->setValue(allowed); + futPuBkey_.reset(); + return true; +} + +bool BsServerAdapter::processTimeout(const std::string& id) +{ + const auto& itTO = timeouts_.find(id); + if (itTO == timeouts_.end()) { + logger_->error("[{}] unknown timeout {}", __func__, id); + return true; + } + itTO->second(); + timeouts_.erase(itTO); + return true; +} + +bool BsServerAdapter::processOpenConnection() +{ + if (connected_) { + logger_->error("[{}] already connected", __func__); + return true; + } +#if 0 + bsClient_ = std::make_unique(logger_, this); +#endif + bs::network::BIP15xParams params; + params.ephemeralPeers = true; + params.authMode = bs::network::BIP15xAuthMode::OneWay; + const auto& bip15xTransport = std::make_shared(logger_, params); + bip15xTransport->setKeyCb([this](const std::string & oldKey, const std::string & newKey + , const std::string & srvAddrPort, const std::shared_ptr> &prompt) { + prompt->setValue(true); + return; //TODO: remove this and above after implementing GUI support for the code below + futPuBkey_ = prompt; + BsServerMessage msg; + auto msgReq = msg.mutable_pub_new_key_request(); + msgReq->set_old_key(oldKey); + msgReq->set_new_key(newKey); + msgReq->set_server_id(srvAddrPort); + pushBroadcast(user_, msg.SerializeAsString(), true); + }); + + auto wsConnection = std::make_unique(logger_, WsDataConnectionParams{}); + auto connection = std::make_unique(logger_, std::move(wsConnection), bip15xTransport); +#if 0 + if (!connection->openConnection(PubKeyLoader::serverHostName(PubKeyLoader::KeyType::Proxy, envConfig_) + , PubKeyLoader::serverHttpPort(), bsClient_.get())) { + logger_->error("[{}] failed to set up connection to {}", __func__ + , PubKeyLoader::serverHostName(PubKeyLoader::KeyType::Proxy, envConfig_)); + return false; //TODO: send negative result response, maybe? + } + bsClient_->setConnection(std::move(connection)); +#endif + return true; +} + +bool BsServerAdapter::processStartLogin(const std::string& login) +{ + if (!connected_) { + return false; // wait for connection to complete + } + if (currentLogin_.empty()) { + currentLogin_ = login; +#if 0 + bsClient_->startLogin(login); +#endif + } + else { + //onStartLoginDone(true, {}); + } + return true; +} + +bool BsServerAdapter::processCancelLogin() +{ + if (currentLogin_.empty()) { + logger_->warn("[BsServerAdapter::processCancelLogin] no login started - ignoring request"); + return true; + } +#if 0 + bsClient_->cancelLogin(); +#endif + return true; +} + +#if 0 +void BsServerAdapter::startTimer(std::chrono::milliseconds timeout + , const std::function&cb) +{ + BsServerMessage msg; + const auto& toKey = CryptoPRNG::generateRandom(4).toHexStr(); + timeouts_[toKey] = cb; + msg.set_timeout(toKey); + const auto& timeNow = bs::message::bus_clock::now(); + pushRequest(user_, user_, msg.SerializeAsString(), timeNow + timeout); +} + +void BsServerAdapter::onStartLoginDone(bool success, const std::string& errorMsg) +{ + if (currentLogin_.empty()) { + logger_->error("[{}] no pending login", __func__); + return; + } + BsServerMessage msg; + auto msgResp = msg.mutable_start_login_result(); + msgResp->set_login(currentLogin_); + msgResp->set_success(success); + msgResp->set_error_text(errorMsg); + pushBroadcast(user_, msg.SerializeAsString()); + + if (success) { + bsClient_->getLoginResult(); + } + else { + currentLogin_.clear(); + } +} + +void BsServerAdapter::onGetLoginResultDone(const BsClientLoginResult& result) +{ + if (currentLogin_.empty()) { + logger_->error("[{}] no pending login", __func__); + return; + } + BsServerMessage msg; + auto msgResp = msg.mutable_login_result(); + msgResp->set_login(currentLogin_); + msgResp->set_status((int)result.status); + msgResp->set_user_type((int)result.userType); + msgResp->set_error_text(result.errorMsg); + msgResp->set_celer_login(result.celerLogin); + msgResp->set_chat_token(result.chatTokenData.toBinStr()); + msgResp->set_chat_token_signature(result.chatTokenSign.toBinStr()); + msgResp->set_bootstrap_signed_data(result.bootstrapDataSigned); + msgResp->set_enabled(result.enabled); + msgResp->set_fee_rate(result.feeRatePb); + auto msgTradeSet = msgResp->mutable_trade_settings(); + msgTradeSet->set_xbt_tier1_limit(result.tradeSettings.xbtTier1Limit); + msgTradeSet->set_xbt_price_band(result.tradeSettings.xbtPriceBand); + msgTradeSet->set_auth_reqd_settl_trades(result.tradeSettings.authRequiredSettledTrades); + msgTradeSet->set_auth_submit_addr_limit(result.tradeSettings.authSubmitAddressLimit); + msgTradeSet->set_dealer_auth_submit_addr_limit(result.tradeSettings.dealerAuthSubmitAddressLimit); + pushBroadcast(user_, msg.SerializeAsString()); + + SettingsMessage msgSet; + msgSet.set_load_bootstrap(result.bootstrapDataSigned); + pushRequest(user_, userSettings_, msgSet.SerializeAsString()); + + MatchingMessage msgMtch; + auto msgReq = msgMtch.mutable_login(); + msgReq->set_matching_login(result.celerLogin); + msgReq->set_terminal_login(currentLogin_); + pushRequest(user_, userMtch_, msgMtch.SerializeAsString()); + + WalletsMessage msgWlt; + msgWlt.set_set_settlement_fee(result.feeRatePb); + pushRequest(user_, userWallets_, msgWlt.SerializeAsString()); + + //TODO: send chat login + currentLogin_.clear(); +} + +void BsServerAdapter::onProcessPbMessage(const Blocksettle::Communication::ProxyTerminalPb::Response& response) +{ + switch (response.data_case()) { + case Blocksettle::Communication::ProxyTerminalPb::Response::kUpdateOrders: + processUpdateOrders(response.update_orders()); + break; + case Blocksettle::Communication::ProxyTerminalPb::Response::kSendUnsignedPayin: + processUnsignedPayin(response.send_unsigned_payin()); + break; + case Blocksettle::Communication::ProxyTerminalPb::Response::kSignPayout: + processSignPayout(response.sign_payout()); + break; + case Blocksettle::Communication::ProxyTerminalPb::Response::kSignPayin: + processSignPayin(response.sign_payin()); + break; + default: + break; + } +} + +void BsServerAdapter::Connected() +{ + connected_ = true; + BsServerMessage msg; + msg.mutable_connected(); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void BsServerAdapter::Disconnected() +{ + connected_ = false; + BsServerMessage msg; + msg.mutable_disconnected(); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void BsServerAdapter::onConnectionFailed() +{ + Disconnected(); +} + +void BsServerAdapter::onBalanceUpdated(const std::string& currency, double balance) +{ + BsServerMessage msg; + auto msgBal = msg.mutable_balance_updated(); + msgBal->set_currency(currency); + msgBal->set_value(balance); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void BsServerAdapter::onTradingStatusChanged(bool tradingEnabled) +{ + BsServerMessage msg; + msg.set_trading_enabled(tradingEnabled); + pushBroadcast(user_, msg.SerializeAsString()); +} +#endif diff --git a/Core/BsServerAdapter.h b/Core/BsServerAdapter.h new file mode 100644 index 000000000..27480f787 --- /dev/null +++ b/Core/BsServerAdapter.h @@ -0,0 +1,85 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef BS_SERVER_ADAPTER_H +#define BS_SERVER_ADAPTER_H + +#include "ApplicationSettings.h" +#include "FutureValue.h" +#include "Message/Adapter.h" + +namespace spdlog { + class logger; +} +namespace Blocksettle { + namespace Communication { + namespace ProxyTerminalPb { + class Response_SignPayinRequest; + class Response_SignPayoutRequest; + class Response_UpdateOrders; + class Response_UnsignedPayinRequest; + } + } +} +namespace BlockSettle { + namespace Terminal { + class BsServerMessage_XbtTransaction; + class SettingsMessage_SettingsResponse; + } +} +class ConnectionManager; +class RequestReplyCommand; + +class BsServerAdapter : public bs::message::Adapter +{ +public: + BsServerAdapter(const std::shared_ptr &); + ~BsServerAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "BS Servers"; } + +private: + void start(); + bool processOwnRequest(const bs::message::Envelope &); + bool processLocalSettings(const BlockSettle::Terminal::SettingsMessage_SettingsResponse &); + bool processPuBKeyResponse(bool); + bool processTimeout(const std::string& id); + bool processOpenConnection(); + bool processStartLogin(const std::string&); + bool processCancelLogin(); + //bool processSubmitAuthAddr(const bs::message::Envelope&, const std::string &addr); + //void processUpdateOrders(const Blocksettle::Communication::ProxyTerminalPb::Response_UpdateOrders&); + //void processUnsignedPayin(const Blocksettle::Communication::ProxyTerminalPb::Response_UnsignedPayinRequest&); + //void processSignPayin(const Blocksettle::Communication::ProxyTerminalPb::Response_SignPayinRequest&); + //void processSignPayout(const Blocksettle::Communication::ProxyTerminalPb::Response_SignPayoutRequest&); + + //bool processOutUnsignedPayin(const BlockSettle::Terminal::BsServerMessage_XbtTransaction&); + //bool processOutSignedPayin(const BlockSettle::Terminal::BsServerMessage_XbtTransaction&); + //bool processOutSignedPayout(const BlockSettle::Terminal::BsServerMessage_XbtTransaction&); + +private: + std::shared_ptr logger_; + std::shared_ptr user_, userSettl_, userSettings_; + std::shared_ptr userMtch_, userWallets_; + std::shared_ptr connMgr_; + ApplicationSettings::EnvConfiguration envConfig_{ ApplicationSettings::EnvConfiguration::Unknown }; + bool connected_{ false }; + std::string currentLogin_; + + std::shared_ptr> futPuBkey_; + std::unordered_map> timeouts_; +}; + + +#endif // BS_SERVER_ADAPTER_H diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt new file mode 100644 index 000000000..2bd9414ef --- /dev/null +++ b/Core/CMakeLists.txt @@ -0,0 +1,45 @@ +# +# +# *********************************************************************************** +# * Copyright (C) 2020 - 2021, BlockSettle AB +# * Distributed under the GNU Affero General Public License (AGPL v3) +# * See LICENSE or http://www.gnu.org/licenses/agpl.html +# * +# ********************************************************************************** +# +# + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +PROJECT(${TERMINAL_CORE_NAME}) + +FILE(GLOB SOURCES + *.cpp +) +FILE(GLOB HEADERS + *.h +) + +INCLUDE_DIRECTORIES(${COMMON_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${COMMON_UI_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${BOTAN_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${WALLET_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${BLOCKSETTLE_UI_INCLUDE_DIR}) # temporary - until some files reside in old structure + +ADD_LIBRARY(${TERMINAL_CORE_NAME} + ${SOURCES} + ${HEADERS} +) + +TARGET_INCLUDE_DIRECTORIES(${TERMINAL_CORE_NAME} + PUBLIC ${BLOCK_SETTLE_ROOT}/Core + PRIVATE ${BLOCK_SETTLE_ROOT}/common/BlocksettleNetworkingLib +) + +TARGET_LINK_LIBRARIES(${TERMINAL_CORE_NAME} + ${BS_NETWORK_LIB_NAME} + ${CRYPTO_LIB_NAME} + Qt5::Network + Qt5::Core +) +# Qt5::Sql diff --git a/Core/MDHistAdapter.cpp b/Core/MDHistAdapter.cpp new file mode 100644 index 000000000..92ae0aa88 --- /dev/null +++ b/Core/MDHistAdapter.cpp @@ -0,0 +1,47 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "MDHistAdapter.h" +#include +#include "TerminalMessage.h" + +#include "common.pb.h" + +using namespace BlockSettle::Common; +using namespace bs::message; + + +MDHistAdapter::MDHistAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(bs::message::TerminalUsers::MDHistory)) +{} + +bool MDHistAdapter::process(const bs::message::Envelope &env) +{ + return true; +} + +bool MDHistAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}", __func__, env.foreignId()); + return false; + } + if (msg.data_case() == AdministrativeMessage::kStart) { + AdministrativeMessage admMsg; + admMsg.set_component_loading(user_->value()); + pushBroadcast(UserTerminal::create(TerminalUsers::System) + , admMsg.SerializeAsString()); + } + } + return false; +} diff --git a/Core/MDHistAdapter.h b/Core/MDHistAdapter.h new file mode 100644 index 000000000..3737b248e --- /dev/null +++ b/Core/MDHistAdapter.h @@ -0,0 +1,40 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef MDHIST_ADAPTER_H +#define MDHIST_ADAPTER_H + +#include "Message/Adapter.h" + +namespace spdlog { + class logger; +} + +class MDHistAdapter : public bs::message::Adapter +{ +public: + MDHistAdapter(const std::shared_ptr &); + ~MDHistAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "MDHistory"; } + +private: + +private: + std::shared_ptr logger_; + std::shared_ptr user_; +}; + + +#endif // MDHIST_ADAPTER_H diff --git a/Core/MktDataAdapter.cpp b/Core/MktDataAdapter.cpp new file mode 100644 index 000000000..b5e153027 --- /dev/null +++ b/Core/MktDataAdapter.cpp @@ -0,0 +1,193 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "MktDataAdapter.h" +#include +#include "BSMarketDataProvider.h" +#include "ConnectionManager.h" +#include "PubKeyLoader.h" +#include "SslCaBundle.h" +#include "TerminalMessage.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + + +MktDataAdapter::MktDataAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(bs::message::TerminalUsers::MktData)) +{ + auto connMgr = std::make_shared(logger_); + connMgr->setCaBundle(bs::caBundlePtr(), bs::caBundleSize()); + mdProvider_ = std::make_shared(connMgr, logger_, this + , true, false); +} + +bool MktDataAdapter::process(const bs::message::Envelope &env) +{ + if (env.receiver->value() == user_->value()) { + MktDataMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own request #{}" + , __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MktDataMessage::kStartConnection: + return processStartConnection(msg.start_connection()); + default: + logger_->warn("[{}] unknown md request {}", __func__, msg.data_case()); + break; + } + } + return true; +} + +bool MktDataAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}" + , __func__, env.foreignId()); + return false; + } + if (msg.data_case() == AdministrativeMessage::kStart) { + AdministrativeMessage admMsg; + admMsg.set_component_loading(user_->value()); + pushBroadcast(UserTerminal::create(TerminalUsers::System) + , admMsg.SerializeAsString()); + return true; + } + } + return false; +} + +void MktDataAdapter::userWantsToConnect() +{ + logger_->debug("[{}]", __func__); +} + +void MktDataAdapter::waitingForConnectionDetails() +{ + logger_->debug("[{}]", __func__); +} + +void MktDataAdapter::connected() +{ + connected_ = true; + MktDataMessage msg; + msg.mutable_connected(); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void MktDataAdapter::disconnected() +{ + connected_ = false; + MktDataMessage msg; + msg.mutable_disconnected(); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void MktDataAdapter::onMDUpdate(bs::network::Asset::Type at, const std::string& name + , bs::network::MDFields fields) +{ + MktDataMessage msg; + auto msgPrices = msg.mutable_price_update(); + auto msgSecurity = msgPrices->mutable_security(); + msgSecurity->set_name(name); + msgSecurity->set_asset_type((int)at); + for (const auto& field : fields) { + switch (field.type) { + case bs::network::MDField::PriceOffer: + msgPrices->set_ask(field.value); + break; + case bs::network::MDField::PriceBid: + msgPrices->set_bid(field.value); + break; + case bs::network::MDField::PriceLast: + msgPrices->set_last(field.value); + break; + case bs::network::MDField::DailyVolume: + msgPrices->set_volume(field.value); + break; + case bs::network::MDField::MDTimestamp: + msgPrices->set_timestamp((uint64_t)field.value); + break; + default: break; + } + } + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +void MktDataAdapter::onMDSecurityReceived(const std::string& name, const bs::network::SecurityDef& sd) +{ + MktDataMessage msg; + auto msgBC = msg.mutable_new_security(); + msgBC->set_name(name); + msgBC->set_asset_type((int)sd.assetType); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void MktDataAdapter::allSecuritiesReceived() +{ + MktDataMessage msg; + auto msgBC = msg.mutable_all_instruments_received(); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void MktDataAdapter::onNewFXTrade(const bs::network::NewTrade& trade) +{ + sendTrade(trade); +} + +void MktDataAdapter::onNewXBTTrade(const bs::network::NewTrade& trade) +{ + sendTrade(trade); +} + +void MktDataAdapter::onNewPMTrade(const bs::network::NewPMTrade& trade) +{ + MktDataMessage msg; + auto msgTrade = msg.mutable_trade(); + msgTrade->set_product(trade.product); + msgTrade->set_price(trade.price); + msgTrade->set_amount(trade.amount); + msgTrade->set_timestamp(trade.timestamp); + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +void MktDataAdapter::sendTrade(const bs::network::NewTrade& trade) +{ + MktDataMessage msg; + auto msgTrade = msg.mutable_trade(); + msgTrade->set_product(trade.product); + msgTrade->set_price(trade.price); + msgTrade->set_amount(trade.amount); + msgTrade->set_timestamp(trade.timestamp); + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +bool MktDataAdapter::processStartConnection(int e) +{ + if (connected_) { + logger_->debug("[{}] already connected", __func__); + return true; + } + const auto env = static_cast(e); + mdProvider_->SetConnectionSettings(PubKeyLoader::serverHostName(PubKeyLoader::KeyType::MdServer, env) + , PubKeyLoader::serverHttpsPort()); + mdProvider_->MDLicenseAccepted(); + return true; +} diff --git a/Core/MktDataAdapter.h b/Core/MktDataAdapter.h new file mode 100644 index 000000000..e2bee9398 --- /dev/null +++ b/Core/MktDataAdapter.h @@ -0,0 +1,63 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef MKT_DATA_ADAPTER_H +#define MKT_DATA_ADAPTER_H + +#include "MarketDataProvider.h" +#include "Message/Adapter.h" + +namespace spdlog { + class logger; +} +class BSMarketDataProvider; + +class MktDataAdapter : public bs::message::Adapter, public MDCallbackTarget +{ +public: + MktDataAdapter(const std::shared_ptr &); + ~MktDataAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "MktData"; } + +protected: //MD callbacks override + void userWantsToConnect() override; + void waitingForConnectionDetails() override; + + void connected() override; + void disconnected() override; + + void onMDUpdate(bs::network::Asset::Type, const std::string& + , bs::network::MDFields) override; + void onMDSecurityReceived(const std::string& + , const bs::network::SecurityDef&) override; + void allSecuritiesReceived() override; + + void onNewFXTrade(const bs::network::NewTrade&) override; + void onNewXBTTrade(const bs::network::NewTrade&) override; + void onNewPMTrade(const bs::network::NewPMTrade&) override; + +private: + void sendTrade(const bs::network::NewTrade&); + bool processStartConnection(int env); + +private: + std::shared_ptr logger_; + std::shared_ptr user_; + std::shared_ptr mdProvider_; + bool connected_{ false }; +}; + + +#endif // MKT_DATA_ADAPTER_H diff --git a/Core/SettingsAdapter.cpp b/Core/SettingsAdapter.cpp new file mode 100644 index 000000000..504efd497 --- /dev/null +++ b/Core/SettingsAdapter.cpp @@ -0,0 +1,778 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "SettingsAdapter.h" +#include +#include +#include +#include "ApplicationSettings.h" +#include "ArmoryServersProvider.h" +#include "BootstrapDataManager.h" +#include "Settings/SignersProvider.h" +#include "LogManager.h" +#include "PubKeyLoader.h" +#include "WsConnection.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + + +SettingsAdapter::SettingsAdapter(const std::shared_ptr &settings + , const QStringList &appArgs) + : appSettings_(settings) + , user_(std::make_shared(TerminalUsers::Settings)) +{ + if (!appSettings_->LoadApplicationSettings(appArgs)) { + std::cerr << "failed to load settings: " << appSettings_->ErrorText().toStdString() << "\n"; + throw std::runtime_error("failed to load settings: " + appSettings_->ErrorText().toStdString()); + } + logMgr_ = std::make_shared(); + logMgr_->add(appSettings_->GetLogsConfig()); + logger_ = logMgr_->logger(); + + if (!appSettings_->get(ApplicationSettings::initialized)) { + appSettings_->SetDefaultSettings(true); + } + appSettings_->selectNetwork(); + logger_->debug("Settings loaded from {}", appSettings_->GetSettingsPath().toStdString()); + + bootstrapDataManager_ = std::make_shared(logMgr_->logger(), appSettings_); + if (bootstrapDataManager_->hasLocalFile()) { + bootstrapDataManager_->loadFromLocalFile(); + } else { + // load from resources + const QString filePathInResources = appSettings_->bootstrapResourceFileName(); + + QFile file; + file.setFileName(filePathInResources); + if (file.open(QIODevice::ReadOnly)) { + const std::string bootstrapData = file.readAll().toStdString(); + if (!bootstrapDataManager_->setReceivedData(bootstrapData)) { + logger_->error("[SettingsAdapter] invalid bootstrap data: {}" + , filePathInResources.toStdString()); + } + } else { + logger_->error("[SettingsAdapter] failed to locate bootstrap file in resources: {}" + , filePathInResources.toStdString()); + } + } + + armoryServersProvider_ = std::make_shared(settings, bootstrapDataManager_); + signersProvider_ = std::make_shared(appSettings_); +} + +bool SettingsAdapter::process(const bs::message::Envelope &env) +{ + if (env.receiver->value() == TerminalUsers::Settings) { + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kGetRequest: + return processGetRequest(env, msg.get_request()); + case SettingsMessage::kPutRequest: + return processPutRequest(msg.put_request()); + case SettingsMessage::kArmoryServer: + return processArmoryServer(msg.armory_server()); + case SettingsMessage::kSetArmoryServer: + return processSetArmoryServer(env, msg.set_armory_server()); + case SettingsMessage::kArmoryServersGet: + return processGetArmoryServers(env); + case SettingsMessage::kAddArmoryServer: + return processAddArmoryServer(env, msg.add_armory_server()); + case SettingsMessage::kDelArmoryServer: + return processDelArmoryServer(env, msg.del_armory_server()); + case SettingsMessage::kUpdArmoryServer: + return processUpdArmoryServer(env, msg.upd_armory_server()); + case SettingsMessage::kSignerRequest: + return processSignerSettings(env); + case SettingsMessage::kSignerSetKey: + return processSignerSetKey(msg.signer_set_key()); + case SettingsMessage::kSignerReset: + return processSignerReset(); + case SettingsMessage::kSignerServersGet: + return processGetSigners(env); + case SettingsMessage::kSetSignerServer: + return processSetSigner(env, msg.set_signer_server()); + case SettingsMessage::kAddSignerServer: + return processAddSigner(env, msg.add_signer_server()); + case SettingsMessage::kDelSignerServer: + return processDelSigner(env, msg.del_signer_server()); + case SettingsMessage::kStateGet: + return processGetState(env); + case SettingsMessage::kReset: + return processReset(env, msg.reset()); + case SettingsMessage::kResetToState: + return processResetToState(env, msg.reset_to_state()); + case SettingsMessage::kLoadBootstrap: + return processBootstrap(env, msg.load_bootstrap()); + case SettingsMessage::kGetBootstrap: + return processBootstrap(env, {}); + case SettingsMessage::kApiPrivkey: + return processApiPrivKey(env); + case SettingsMessage::kApiClientKeys: + return processApiClientsList(env); + default: + logger_->warn("[SettingsAdapter::process] unknown data case: {}" + , msg.data_case()); + break; + } + } + return true; +} + +bool SettingsAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->value() == TerminalUsers::Blockchain) { + ArmoryMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse armory msg #{}", __func__, env.foreignId()); + return false; + } + if (msg.data_case() == ArmoryMessage::kSettingsRequest) { + ArmoryMessage msgReply; + auto msgResp = msgReply.mutable_settings_response(); + const auto& armorySettings = armoryServersProvider_->getArmorySettings(); + armoryServersProvider_->setConnectedArmorySettings(armorySettings); + msgResp->set_socket_type(armorySettings.socketType); + msgResp->set_network_type((int)armorySettings.netType); + msgResp->set_host(armorySettings.armoryDBIp.toStdString()); + msgResp->set_port(std::to_string(armorySettings.armoryDBPort)); + msgResp->set_bip15x_key(armorySettings.armoryDBKey.toStdString()); + msgResp->set_run_locally(armorySettings.runLocally); + msgResp->set_data_dir(armorySettings.dataDir.toStdString()); + msgResp->set_executable_path(armorySettings.armoryExecutablePath.toStdString()); + msgResp->set_bitcoin_dir(armorySettings.bitcoinBlocksDir.toStdString()); + msgResp->set_db_dir(armorySettings.dbDir.toStdString()); + msgResp->set_cache_file_name(appSettings_->get(ApplicationSettings::txCacheFileName)); + pushResponse(user_, env, msgReply.SerializeAsString()); + return true; + } + } + return false; +} + +bool SettingsAdapter::processRemoteSettings(uint64_t msgId) +{ + const auto &itReq = remoteSetReqs_.find(msgId); + if (itReq == remoteSetReqs_.end()) { + logger_->error("[{}] failed to find remote settings request #{}", __func__ + , msgId); + return true; + } + return true; +} + +bool SettingsAdapter::processGetState(const bs::message::Envelope& env) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_state(); + for (const auto& st : appSettings_->getState()) { + auto setResp = msgResp->add_responses(); + auto setReq = setResp->mutable_request(); + setReq->set_source(SettingSource_Local); + setReq->set_index(static_cast(st.first)); + setFromQVariant(st.second, setReq, setResp); + } + return pushResponse(user_, env, msg.SerializeAsString()); +} + +bool SettingsAdapter::processReset(const bs::message::Envelope& env + , const SettingsMessage_SettingsRequest& request) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_state(); + for (const auto& req : request.requests()) { + auto setResp = msgResp->add_responses(); + auto setReq = setResp->mutable_request(); + setReq->set_source(req.source()); + setReq->set_index(req.index()); + const auto& setting = static_cast(req.index()); + appSettings_->reset(setting); + const auto& value = appSettings_->get(setting); + setFromQVariant(value, setReq, setResp); + } + return pushResponse(user_, env, msg.SerializeAsString()); +} + +bool SettingsAdapter::processResetToState(const bs::message::Envelope& env + , const SettingsMessage_SettingsResponse& request) +{ + for (const auto& req : request.responses()) { + const auto& value = fromResponse(req); + const auto& setting = static_cast(req.request().index()); + appSettings_->set(setting, value); + } + SettingsMessage msg; + *msg.mutable_state() = request; + return pushResponse(user_, env, msg.SerializeAsString()); +} + +bool SettingsAdapter::processBootstrap(const bs::message::Envelope &env + , const std::string& bsData) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_bootstrap(); + bool result = true; + if (!bsData.empty()) { + result = bootstrapDataManager_->setReceivedData(bsData); + } + msgResp->set_loaded(result); + if (result) { + for (const auto& validationAddr : bootstrapDataManager_->GetAuthValidationList()) { + msgResp->add_auth_validations(validationAddr); + } + for (const auto& ccDef : bootstrapDataManager_->GetCCDefinitions()) { + auto msgCcDef = msgResp->add_cc_definitions(); + msgCcDef->set_security_id(ccDef.securityId); + msgCcDef->set_product(ccDef.product); + msgCcDef->set_genesis_address(ccDef.genesisAddr.display()); + msgCcDef->set_lot_size(ccDef.nbSatoshis); + } + } + else { + logger_->error("[{}] failed to set bootstrap data", __func__); + } + return pushResponse(user_, bsData.empty() ? env.sender : nullptr + , msg.SerializeAsString(), bsData.empty() ? env.foreignId() : 0); +} + +static bs::network::ws::PrivateKey readOrCreatePrivateKey(const std::string& filename) +{ + bs::network::ws::PrivateKey result; + std::ifstream privKeyReader(filename, std::ios::binary); + if (privKeyReader.is_open()) { + std::string str; + str.assign(std::istreambuf_iterator(privKeyReader) + , std::istreambuf_iterator()); + result.reserve(str.size()); + std::for_each(str.cbegin(), str.cend(), [&result](char c) { + result.push_back(c); + }); + } + if (result.empty()) { + result = bs::network::ws::generatePrivKey(); + std::ofstream privKeyWriter(filename, std::ios::out | std::ios::binary); + privKeyWriter.write((char*)&result[0], result.size()); + const auto& pubKey = bs::network::ws::publicKey(result); + std::ofstream pubKeyWriter(filename + ".pub", std::ios::out | std::ios::binary); + pubKeyWriter << pubKey; + } + return result; +} + +bool SettingsAdapter::processApiPrivKey(const bs::message::Envelope& env) +{ //FIXME: should be re-implemented to avoid storing plain private key in a file on disk + const auto &apiKeyFN = appSettings_->AppendToWritableDir( + QString::fromStdString("apiPrivKey")).toStdString(); + const auto &apiPrivKey = readOrCreatePrivateKey(apiKeyFN); + SettingsMessage msg; + SecureBinaryData privKey(apiPrivKey.data(), apiPrivKey.size()); + msg.set_api_privkey(privKey.toBinStr()); + return pushResponse(user_, env, msg.SerializeAsString()); +} + +static std::set readClientKeys(const std::string& filename) +{ + std::ifstream in(filename); + if (!in.is_open()) { + return {}; + } + std::set result; + std::string str; + while (std::getline(in, str)) { + if (str.empty()) { + continue; + } + try { + const auto& pubKey = BinaryData::CreateFromHex(str).toBinStr(); + result.insert(pubKey); + } catch (const std::exception&) {} // ignore invalid keys for now + } + return result; +} + +bool SettingsAdapter::processApiClientsList(const bs::message::Envelope& env) +{ + const auto& apiKeysFN = appSettings_->AppendToWritableDir( + QString::fromStdString("apiClientPubKeys")).toStdString(); + const auto& clientKeys = readClientKeys(apiKeysFN); + if (clientKeys.empty()) { + logger_->debug("[{}] no API client keys found", __func__); + } + SettingsMessage msg; + const auto msgResp = msg.mutable_api_client_keys(); + for (const auto& clientKey : clientKeys) { + msgResp->add_pub_keys(clientKey); + } + return pushResponse(user_, env, msg.SerializeAsString()); +} + +std::string SettingsAdapter::guiMode() const +{ + if (!appSettings_) { + return {}; + } + return appSettings_->guiMode().toStdString(); +} + +bool SettingsAdapter::processGetRequest(const bs::message::Envelope &env + , const SettingsMessage_SettingsRequest &request) +{ + unsigned int nbFetched = 0; + SettingsMessage msg; + auto msgResp = msg.mutable_get_response(); + for (const auto &req : request.requests()) { + auto resp = msgResp->add_responses(); + resp->set_allocated_request(new SettingRequest(req)); + if (req.source() == SettingSource_Local) { + switch (req.index()) { + case SetIdx_BlockSettleSignAddress: + resp->set_s(appSettings_->GetBlocksettleSignAddress()); + resp->mutable_request()->set_type(SettingType_String); + break; + default: { + const auto setting = static_cast(req.index()); + switch (req.type()) { + case SettingType_Unknown: { + const auto& value = appSettings_->get(setting); + switch (value.type()) { + case QVariant::Type::String: + resp->set_s(value.toString().toStdString()); + resp->mutable_request()->set_type(SettingType_String); + break; + default: // normally string is used for all unknown values + logger_->warn("[{}] {}: unhandled QVariant type: {}", __func__ + , (int)setting, (int)value.type()); + break; + } + } + break; + case SettingType_String: + resp->set_s(appSettings_->get(setting)); + break; + case SettingType_Int: + resp->set_i(appSettings_->get(setting)); + break; + case SettingType_UInt: + resp->set_ui(appSettings_->get(setting)); + break; + case SettingType_UInt64: + resp->set_ui64(appSettings_->get(setting)); + break; + case SettingType_Bool: + resp->set_b(appSettings_->get(setting)); + break; + case SettingType_Float: + resp->set_f(appSettings_->get(setting)); + break; + case SettingType_Rect: { + const auto& rect = appSettings_->get(setting); + auto r = resp->mutable_rect(); + r->set_left(rect.left()); + r->set_top(rect.top()); + r->set_width(rect.width()); + r->set_height(rect.height()); + } + break; + case SettingType_Strings: { + const auto& strings = appSettings_->get(setting); + auto ss = resp->mutable_strings(); + for (const auto& string : strings) { + ss->add_strings(string.toStdString()); + } + } + break; + case SettingType_StrMap: { + const auto& map = appSettings_->get(setting); + auto keyVals = resp->mutable_key_vals(); + for (auto it = map.begin(); it != map.end(); it++) { + auto sm = keyVals->add_key_vals(); + sm->set_key(it.key().toStdString()); + sm->set_value(it.value().toString().toStdString()); + } + } + break; + default: + logger_->error("[{}] unknown setting type: {}", __func__, (int)req.type()); + nbFetched--; + break; + } + } + break; + } + nbFetched++; + } + else { + logger_->error("[{}] unknown settings source: {}", __func__, req.source()); + } + } + if (nbFetched > 0) { + return pushResponse(user_, env, msg.SerializeAsString()); + } + return true; +} + +bool SettingsAdapter::processPutRequest(const SettingsMessage_SettingsResponse &request) +{ + unsigned int nbUpdates = 0; + for (const auto &req : request.responses()) { + if (req.request().source() == SettingSource_Local) { + const auto setting = static_cast(req.request().index()); + switch (req.request().type()) { + case SettingType_String: + appSettings_->set(setting, QString::fromStdString(req.s())); + break; + case SettingType_Int: + appSettings_->set(setting, req.i()); + break; + case SettingType_UInt: + appSettings_->set(setting, req.ui()); + break; + case SettingType_UInt64: + appSettings_->set(setting, quint64(req.ui64())); + break; + case SettingType_Bool: + appSettings_->set(setting, req.b()); + break; + case SettingType_Float: + appSettings_->set(setting, req.f()); + break; + case SettingType_Rect: { + QRect rect(req.rect().left(), req.rect().top(), req.rect().width() + , req.rect().height()); + appSettings_->set(setting, rect); + } + break; + case SettingType_Strings: { + QStringList strings; + for (const auto &s : req.strings().strings()) { + strings << QString::fromStdString(s); + } + appSettings_->set(setting, strings); + } + break; + case SettingType_StrMap: { + QVariantMap map; + for (const auto &keyVal : req.key_vals().key_vals()) { + map[QString::fromStdString(keyVal.key())] = QString::fromStdString(keyVal.value()); + } + appSettings_->set(setting, map); + } + break; + default: + logger_->error("[{}] unknown setting type: {}", __func__, (int)req.request().type()); + nbUpdates--; + break; + } + nbUpdates++; + } + else { + logger_->warn("[{}] unknown source for setting ({})", __func__ + , req.request().index()); + continue; + } + } + if (nbUpdates) { + SettingsMessage msg; + *(msg.mutable_settings_updated()) = request; + return pushBroadcast(user_, msg.SerializeAsString()); + } + return true; +} + +bool SettingsAdapter::processArmoryServer(const BlockSettle::Terminal::SettingsMessage_ArmoryServer &request) +{ + int selIndex = 0; + for (const auto &server : armoryServersProvider_->servers()) { + if ((server.name == QString::fromStdString(request.server_name())) + && (server.netType == static_cast(request.network_type()))) { + break; + } + selIndex++; + } + if (selIndex >= armoryServersProvider_->servers().size()) { + logger_->error("[{}] failed to find Armory server {}", __func__, request.server_name()); + return true; + } + armoryServersProvider_->setupServer(selIndex); + appSettings_->selectNetwork(); +} + +bool SettingsAdapter::processSetArmoryServer(const bs::message::Envelope& env, int index) +{ + armoryServersProvider_->setupServer(index); + appSettings_->selectNetwork(); + return processGetArmoryServers(env); +} + +bool SettingsAdapter::processGetArmoryServers(const bs::message::Envelope& env) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_armory_servers(); + msgResp->set_idx_current(armoryServersProvider_->indexOfCurrent()); + msgResp->set_idx_connected(armoryServersProvider_->indexOfConnected()); + for (const auto& server : armoryServersProvider_->servers()) { + auto msgSrv = msgResp->add_servers(); + msgSrv->set_network_type((int)server.netType); + msgSrv->set_server_name(server.name.toStdString()); + msgSrv->set_server_address(server.armoryDBIp.toStdString()); + msgSrv->set_server_port(std::to_string(server.armoryDBPort)); + msgSrv->set_server_key(server.armoryDBKey.toStdString()); + msgSrv->set_run_locally(server.runLocally); + msgSrv->set_one_way_auth(server.oneWayAuth_); + msgSrv->set_password(server.password.toBinStr()); + } + return pushResponse(user_, env, msg.SerializeAsString()); +} + +static ArmoryServer fromMessage(const SettingsMessage_ArmoryServer& msg) +{ + ArmoryServer result; + result.name = QString::fromStdString(msg.server_name()); + result.netType = static_cast(msg.network_type()); + result.armoryDBIp = QString::fromStdString(msg.server_address()); + result.armoryDBPort = std::stoi(msg.server_port()); + result.armoryDBKey = QString::fromStdString(msg.server_key()); + result.password = SecureBinaryData::fromString(msg.password()); + result.runLocally = msg.run_locally(); + result.oneWayAuth_ = msg.one_way_auth(); + return result; +} + +bool SettingsAdapter::processAddArmoryServer(const bs::message::Envelope& env + , const SettingsMessage_ArmoryServer& request) +{ + const auto& server = fromMessage(request); + if (armoryServersProvider_->add(server)) { + armoryServersProvider_->setupServer(armoryServersProvider_->indexOf(server)); + } + else { + logger_->warn("[{}] failed to add server", __func__); + } + return processGetArmoryServers(env); +} + +bool SettingsAdapter::processDelArmoryServer(const bs::message::Envelope& env + , int index) +{ + if (!armoryServersProvider_->remove(index)) { + logger_->warn("[{}] failed to remove server #{}", __func__, index); + } + return processGetArmoryServers(env); +} + +bool SettingsAdapter::processUpdArmoryServer(const bs::message::Envelope& env + , const SettingsMessage_ArmoryServerUpdate& request) +{ + const auto& server = fromMessage(request.server()); + if (!armoryServersProvider_->replace(request.index(), server)) { + logger_->warn("[{}] failed to update server #{}", __func__, request.index()); + } + return processGetArmoryServers(env); +} + +bool SettingsAdapter::processSignerSettings(const bs::message::Envelope &env) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_signer_response(); + msgResp->set_is_local(signersProvider_->currentSignerIsLocal()); + msgResp->set_network_type(appSettings_->get(ApplicationSettings::netType)); + + const auto &signerHost = signersProvider_->getCurrentSigner(); + msgResp->set_name(signerHost.name.toStdString()); + msgResp->set_host(signerHost.address.toStdString()); + msgResp->set_port(std::to_string(signerHost.port)); + msgResp->set_key(signerHost.key.toStdString()); + msgResp->set_id(signerHost.serverId()); + msgResp->set_remote_keys_dir(signersProvider_->remoteSignerKeysDir()); + msgResp->set_remote_keys_file(signersProvider_->remoteSignerKeysFile()); + + for (const auto &signer : signersProvider_->signers()) { + auto keyVal = msgResp->add_client_keys(); + keyVal->set_key(signer.serverId()); + keyVal->set_value(signer.key.toStdString()); + } + msgResp->set_home_dir(appSettings_->GetHomeDir().toStdString()); + msgResp->set_auto_sign_spend_limit(appSettings_->get(ApplicationSettings::autoSignSpendLimit)); + + return pushResponse(user_, env, msg.SerializeAsString()); +} + +bool SettingsAdapter::processSignerSetKey(const SettingsMessage_SignerSetKey &request) +{ + signersProvider_->addKey(request.server_id(), request.new_key()); + return true; +} + +bool SettingsAdapter::processSignerReset() +{ + signersProvider_->setupSigner(0, true); + return true; +} + +bool SettingsAdapter::processGetSigners(const bs::message::Envelope& env) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_signer_servers(); + msgResp->set_own_key(signersProvider_->remoteSignerOwnKey().toHexStr()); + msgResp->set_idx_current(signersProvider_->indexOfCurrent()); + for (const auto& signer : signersProvider_->signers()) { + auto msgSrv = msgResp->add_servers(); + msgSrv->set_name(signer.name.toStdString()); + msgSrv->set_host(signer.address.toStdString()); + msgSrv->set_port(std::to_string(signer.port)); + msgSrv->set_key(signer.key.toStdString()); + } + return pushResponse(user_, env, msg.SerializeAsString()); +} + +bool SettingsAdapter::processSetSigner(const bs::message::Envelope& env + , int index) +{ + signersProvider_->setupSigner(index); + return processGetSigners(env); +} + +static SignerHost fromMessage(const SettingsMessage_SignerServer& msg) +{ + SignerHost result; + result.name = QString::fromStdString(msg.name()); + result.address = QString::fromStdString(msg.host()); + result.port = std::stoi(msg.port()); + result.key = QString::fromStdString(msg.key()); + return result; +} + +bool SettingsAdapter::processAddSigner(const bs::message::Envelope& env + , const SettingsMessage_SignerServer& request) +{ + const auto& signer = fromMessage(request); + signersProvider_->add(signer); + signersProvider_->setupSigner(signersProvider_->indexOf(signer)); + return processGetSigners(env); +} + +bool SettingsAdapter::processDelSigner(const bs::message::Envelope& env + , int index) +{ + signersProvider_->remove(index); + return processGetSigners(env); +} + + +void bs::message::setFromQVariant(const QVariant& val, SettingRequest* req, SettingResponse* resp) +{ + switch (val.type()) { + case QVariant::Type::String: + req->set_type(SettingType_String); + resp->set_s(val.toString().toStdString()); + break; + case QVariant::Type::Int: + req->set_type(SettingType_Int); + resp->set_i(val.toInt()); + break; + case QVariant::Type::UInt: + req->set_type(SettingType_UInt); + resp->set_ui(val.toUInt()); + break; + case QVariant::Type::ULongLong: + case QVariant::Type::LongLong: + req->set_type(SettingType_UInt64); + resp->set_ui64(val.toULongLong()); + break; + case QVariant::Type::Double: + req->set_type(SettingType_Float); + resp->set_f(val.toDouble()); + break; + case QVariant::Type::Bool: + req->set_type(SettingType_Bool); + resp->set_b(val.toBool()); + break; + case QVariant::Type::Rect: + req->set_type(SettingType_Rect); + { + auto setRect = resp->mutable_rect(); + setRect->set_left(val.toRect().left()); + setRect->set_top(val.toRect().top()); + setRect->set_height(val.toRect().height()); + setRect->set_width(val.toRect().width()); + } + break; + case QVariant::Type::StringList: + req->set_type(SettingType_Strings); + for (const auto& s : val.toStringList()) { + resp->mutable_strings()->add_strings(s.toStdString()); + } + break; + case QVariant::Type::Map: + req->set_type(SettingType_StrMap); + for (const auto& key : val.toMap().keys()) { + auto kvData = resp->mutable_key_vals()->add_key_vals(); + kvData->set_key(key.toStdString()); + kvData->set_value(val.toMap()[key].toString().toStdString()); + } + break; + default: break; // ignore other types + } +} + +QVariant bs::message::fromResponse(const BlockSettle::Terminal::SettingResponse& setting) +{ + QVariant value; + switch (setting.request().type()) { + case SettingType_String: + value = QString::fromStdString(setting.s()); + break; + case SettingType_Int: + value = setting.i(); + break; + case SettingType_UInt: + value = setting.ui(); + break; + case SettingType_UInt64: + value = quint64(setting.ui64()); + break; + case SettingType_Bool: + value = setting.b(); + break; + case SettingType_Float: + value = setting.f(); + break; + case SettingType_Rect: + value = QRect(setting.rect().left(), setting.rect().top() + , setting.rect().width(), setting.rect().height()); + break; + case SettingType_Strings: { + QStringList sl; + for (const auto& s : setting.strings().strings()) { + sl << QString::fromStdString(s); + } + value = sl; + } + break; + case SettingType_StrMap: { + QVariantMap vm; + for (const auto& keyVal : setting.key_vals().key_vals()) { + vm[QString::fromStdString(keyVal.key())] = QString::fromStdString(keyVal.value()); + } + value = vm; + } + break; + default: break; + } + return value; +} diff --git a/Core/SettingsAdapter.h b/Core/SettingsAdapter.h new file mode 100644 index 000000000..5999dc89b --- /dev/null +++ b/Core/SettingsAdapter.h @@ -0,0 +1,115 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef SETTINGS_ADAPTER_H +#define SETTINGS_ADAPTER_H + +#include +#include +#include +#include "Message/Adapter.h" +#include "TerminalMessage.h" + +namespace spdlog { + class logger; +} +namespace bs { + class LogManager; + struct TradeSettings; +} +namespace BlockSettle { + namespace Terminal { + class SettingsMessage_ArmoryServer; + class SettingsMessage_ArmoryServerUpdate; + class SettingsMessage_SettingsRequest; + class SettingsMessage_SettingsResponse; + class SettingsMessage_SignerServer; + class SettingsMessage_SignerSetKey; + class SettingRequest; + class SettingResponse; + enum SettingType : int; + } +} +class ApplicationSettings; +class ArmoryServersProvider; +class BootstrapDataManager; +class CCFileManager; +class SignersProvider; + +namespace bs { + namespace message { + void setFromQVariant(const QVariant &, BlockSettle::Terminal::SettingRequest * + , BlockSettle::Terminal::SettingResponse *); + QVariant fromResponse(const BlockSettle::Terminal::SettingResponse &); + } +} + +class SettingsAdapter : public bs::message::Adapter +{ +public: + SettingsAdapter(const std::shared_ptr & + , const QStringList &appArgs); + ~SettingsAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "Settings"; } + + std::shared_ptr logManager() const { return logMgr_; } + std::string guiMode() const; + +private: + bool processGetRequest(const bs::message::Envelope & + , const BlockSettle::Terminal::SettingsMessage_SettingsRequest &); + bool processPutRequest(const BlockSettle::Terminal::SettingsMessage_SettingsResponse &); + bool processArmoryServer(const BlockSettle::Terminal::SettingsMessage_ArmoryServer &); + bool processSetArmoryServer(const bs::message::Envelope&, int index); + bool processGetArmoryServers(const bs::message::Envelope&); + bool processAddArmoryServer(const bs::message::Envelope& + , const BlockSettle::Terminal::SettingsMessage_ArmoryServer&); + bool processDelArmoryServer(const bs::message::Envelope&, int index); + bool processUpdArmoryServer(const bs::message::Envelope& + , const BlockSettle::Terminal::SettingsMessage_ArmoryServerUpdate&); + bool processSignerSettings(const bs::message::Envelope &); + bool processSignerSetKey(const BlockSettle::Terminal::SettingsMessage_SignerSetKey &); + bool processSignerReset(); + bool processGetSigners(const bs::message::Envelope&); + bool processSetSigner(const bs::message::Envelope&, int); + bool processAddSigner(const bs::message::Envelope& + , const BlockSettle::Terminal::SettingsMessage_SignerServer&); + bool processDelSigner(const bs::message::Envelope&, int); + bool processRemoteSettings(uint64_t msgId); + bool processGetState(const bs::message::Envelope&); + bool processReset(const bs::message::Envelope& + , const BlockSettle::Terminal::SettingsMessage_SettingsRequest&); + bool processResetToState(const bs::message::Envelope& + , const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + bool processBootstrap(const bs::message::Envelope&, const std::string&); + bool processApiPrivKey(const bs::message::Envelope&); + bool processApiClientsList(const bs::message::Envelope&); + +private: + std::shared_ptr user_; + std::shared_ptr logMgr_; + std::shared_ptr logger_; + std::shared_ptr appSettings_; + std::shared_ptr bootstrapDataManager_; + std::shared_ptr armoryServersProvider_; + std::shared_ptr signersProvider_; + std::shared_ptr ccFileManager_; + std::shared_ptr tradeSettings_; + + std::map remoteSetReqs_; +}; + + +#endif // SETTINGS_ADAPTER_H diff --git a/Core/SignerAdapter.cpp b/Core/SignerAdapter.cpp new file mode 100644 index 000000000..35b1de300 --- /dev/null +++ b/Core/SignerAdapter.cpp @@ -0,0 +1,611 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "SignerAdapter.h" +#include +#include "Adapters/SignerClient.h" +#include "CoreWalletsManager.h" +#include "Wallets/InprocSigner.h" +#include "Wallets/ProtobufHeadlessUtils.h" +#include "TerminalMessage.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + + +SignerAdapter::SignerAdapter(const std::shared_ptr &logger + , const std::shared_ptr& signer) + : logger_(logger), signer_(signer) + , user_(std::make_shared(bs::message::TerminalUsers::Signer)) +{} + +std::unique_ptr SignerAdapter::createClient() const +{ + auto client = std::make_unique(logger_, user_); + client->setQueue(queue_); + return client; +} + +bool SignerAdapter::process(const bs::message::Envelope &env) +{ + if (env.isRequest()) { + SignerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own msg #{}", __func__, env.foreignId()); + return true; + } + return processOwnRequest(env, msg); + } + else if (env.sender->value() == TerminalUsers::Settings) { + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kSignerResponse: + return processSignerSettings(msg.signer_response()); + case SettingsMessage::kNewKeyResponse: + return processNewKeyResponse(msg.new_key_response()); + default: break; + } + } + return true; +} + +bool SignerAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->value() == TerminalUsers::System) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case AdministrativeMessage::kStart: + case AdministrativeMessage::kRestart: + start(); + return true; + default: break; + } + } + return false; +} + +void SignerAdapter::start() +{ + if (signer_) { + sendComponentLoading(); + onReady(); + walletsReady(); + return; + } + SettingsMessage msg; + msg.mutable_signer_request(); + pushRequest(user_, UserTerminal::create(TerminalUsers::Settings) + , msg.SerializeAsString()); +} + +bool SignerAdapter::processOwnRequest(const bs::message::Envelope &env + , const SignerMessage &request) +{ + switch (request.data_case()) { + case SignerMessage::kStartWalletsSync: + return processStartWalletSync(env); + case SignerMessage::kSyncAddresses: + return processSyncAddresses(env, request.sync_addresses()); + case SignerMessage::kSyncNewAddresses: + return processSyncNewAddresses(env, request.sync_new_addresses()); + case SignerMessage::kExtAddrChain: + return processExtendAddrChain(env, request.ext_addr_chain()); + case SignerMessage::kSyncWallet: + return processSyncWallet(env, request.sync_wallet()); + case SignerMessage::kSyncHdWallet: + return processSyncHdWallet(env, request.sync_hd_wallet()); + case SignerMessage::kSyncAddrComment: + return processSyncAddrComment(request.sync_addr_comment()); + case SignerMessage::kSyncTxComment: + return processSyncTxComment(request.sync_tx_comment()); + case SignerMessage::kGetRootPubkey: + return processGetRootPubKey(env, request.get_root_pubkey()); + case SignerMessage::kDelHdRoot: + return processDelHdRoot(request.del_hd_root()); + case SignerMessage::kDelHdLeaf: + return processDelHdLeaf(request.del_hd_leaf()); + case SignerMessage::kSignTxRequest: + return processSignTx(env, request.sign_tx_request()); + case SignerMessage::kResolvePubSpenders: + return processResolvePubSpenders(env + , bs::signer::pbTxRequestToCore(request.resolve_pub_spenders())); + case SignerMessage::kAutoSign: + return processAutoSignRequest(env, request.auto_sign()); + case SignerMessage::kDialogRequest: + return processDialogRequest(env, request.dialog_request()); + case SignerMessage::kCreateWallet: + return processCreateWallet(env, false, request.create_wallet()); + case SignerMessage::kImportWallet: + return processCreateWallet(env, true, request.import_wallet()); + case SignerMessage::kDeleteWallet: + return processDeleteWallet(env, request.delete_wallet()); + default: + logger_->warn("[{}] unknown signer request: {}", __func__, request.data_case()); + break; + } + return true; +} + +bool SignerAdapter::processSignerSettings(const SettingsMessage_SignerServer &response) +{ + curServerId_ = response.id(); + netType_ = static_cast(response.network_type()); + walletsDir_ = response.home_dir(); + walletsMgr_ = std::make_shared(logger_); + signer_ = std::make_shared(walletsMgr_, logger_, this + , walletsDir_, netType_, [this](const std::string& walletId) + -> std::unique_ptr { + const auto& hdWallet = walletsMgr_->getHDWalletById(walletId); + if (!hdWallet) { + return nullptr; + } + return std::make_unique(hdWallet, passphrase_); + }); + logger_->info("[{}] loading wallets from {}", __func__, walletsDir_); + signer_->Start(); + walletsChanged(); + return sendComponentLoading(); +} + +void SignerAdapter::walletsChanged(bool rescan) +{ + SignerMessage msg; + msg.set_wallets_list_updated(rescan); + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +void SignerAdapter::onReady() +{ + SignerMessage msg; + auto msgState = msg.mutable_state(); + msgState->set_code((int)SignContainer::Ready); + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +void SignerAdapter::walletsReady() +{ + SignerMessage msg; + msg.mutable_wallets_ready_to_sync(); + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +void SignerAdapter::newWalletPrompt() +{ + logger_->debug("[{}]", __func__); + SignerMessage msg; + msg.mutable_need_new_wallet_prompt(); + pushBroadcast(user_, msg.SerializeAsString(), true); +} + +void SignerAdapter::autoSignStateChanged(bs::error::ErrorCode code + , const std::string& walletId) +{ + const auto& itAS = autoSignRequests_.find(walletId); + if (itAS == autoSignRequests_.end()) { + logger_->warn("[{}] no request found for {}", __func__, walletId); + return; + } + bool enabled = false; + if (code == bs::error::ErrorCode::NoError) { + enabled = true; + } + else if (code == bs::error::ErrorCode::AutoSignDisabled) { + enabled = false; + } + else { + logger_->error("[{}] auto sign {} error code: {}", __func__, walletId + , (int)code); + } + SignerMessage msg; + auto msgResp = msg.mutable_auto_sign(); + msgResp->set_wallet_id(walletId); + msgResp->set_enable(enabled); + pushResponse(user_, itAS->second, msg.SerializeAsString()); + autoSignRequests_.erase(itAS); +} + +bool SignerAdapter::sendComponentLoading() +{ + static const auto &adminUser = UserTerminal::create(TerminalUsers::System); + AdministrativeMessage msg; + msg.set_component_loading(user_->value()); + return pushBroadcast(adminUser, msg.SerializeAsString()); +} + +bool SignerAdapter::processNewKeyResponse(bool acceptNewKey) +{ + if (!connFuture_) { + logger_->error("[{}] new key comparison wasn't requested", __func__); + return true; + } + connFuture_->setValue(acceptNewKey); + if (acceptNewKey) { + SettingsMessage msg; + auto msgReq = msg.mutable_signer_set_key(); + msgReq->set_server_id(curServerId_); + msgReq->set_new_key(connKey_); + pushRequest(user_, UserTerminal::create(TerminalUsers::Settings) + , msg.SerializeAsString()); + } + connFuture_.reset(); + return true; +} + +bool SignerAdapter::processStartWalletSync(const bs::message::Envelope &env) +{ + requests_.put(env.foreignId(), env.sender); + const auto &cbWallets = [this, msgId=env.foreignId()] + (const std::vector &wi) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_wallets_info(); + for (const auto &entry : wi) { + auto wallet = msgResp->add_wallets(); + wallet->set_format((int)entry.format); + for (const auto &id : entry.ids) { + wallet->add_ids(id); + } + wallet->set_name(entry.name); + wallet->set_description(entry.description); + wallet->set_network_type((int)entry.netType); + wallet->set_watch_only(entry.watchOnly); + for (const auto &encType : entry.encryptionTypes) { + wallet->add_encryption_types((int)encType); + } + for (const auto &encKey : entry.encryptionKeys) { + wallet->add_encryption_keys(encKey.toBinStr()); + } + auto keyRank = wallet->mutable_encryption_rank(); + keyRank->set_m(entry.encryptionRank.m); + keyRank->set_n(entry.encryptionRank.n); + } + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + signer_->syncWalletInfo(cbWallets); + return true; +} + +bool SignerAdapter::processSyncAddresses(const bs::message::Envelope &env + , const SignerMessage_SyncAddresses &request) +{ + requests_.put(env.foreignId(), env.sender); + const auto &cb = [this, msgId = env.foreignId(), walletId = request.wallet_id()] + (bs::sync::SyncState st) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_sync_addr_result(); + msgResp->set_wallet_id(walletId); + msgResp->set_status(static_cast(st)); + + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + std::set addrSet; + for (const auto &addr : request.addresses()) { + addrSet.insert(BinaryData::fromString(addr)); + } + signer_->syncAddressBatch(request.wallet_id(), addrSet, cb); + return true; +} + +bool SignerAdapter::processSyncNewAddresses(const bs::message::Envelope &env + , const SignerMessage_SyncNewAddresses &request) +{ + requests_.put(env.foreignId(), env.sender); + if (request.single()) { + const auto &cb = [this, msgId = env.foreignId(), walletId = request.wallet_id()] + (const bs::Address &addr) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_new_addresses(); + msgResp->set_wallet_id(walletId); + msgResp->add_addresses()->set_address(addr.display()); + + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + if (request.indices_size() != 1) { + logger_->error("[{}] not a single new address request", __func__); + return true; + } + signer_->syncNewAddress(request.wallet_id(), request.indices(0), cb); + } + else { + const auto &cb = [this, msgId=env.foreignId(), walletId = request.wallet_id()] + (const std::vector> &addrIdxPairs) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_new_addresses(); + msgResp->set_wallet_id(walletId); + for (const auto &aiPair : addrIdxPairs) { + auto msgPair = msgResp->add_addresses(); + msgPair->set_address(aiPair.first.display()); + msgPair->set_index(aiPair.second); + } + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + std::vector indices; + indices.reserve(request.indices_size()); + for (const auto &idx : request.indices()) { + indices.push_back(idx); + } + signer_->syncNewAddresses(request.wallet_id(), indices, cb); + } + return true; +} + +bool SignerAdapter::processExtendAddrChain(const bs::message::Envelope &env + , const SignerMessage_ExtendAddrChain &request) +{ + requests_.put(env.foreignId(), env.sender); + const auto &cb = [this, msgId = env.foreignId(), walletId = request.wallet_id()] + (const std::vector> &addrIdxPairs) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_addr_chain_extended(); + msgResp->set_wallet_id(walletId); + for (const auto &aiPair : addrIdxPairs) { + auto msgPair = msgResp->add_addresses(); + msgPair->set_address(aiPair.first.display()); + msgPair->set_index(aiPair.second); + } + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + signer_->extendAddressChain(request.wallet_id(), request.count(), request.ext_int(), cb); + return true; +} + +bool SignerAdapter::processSyncWallet(const bs::message::Envelope &env + , const std::string &walletId) +{ + requests_.put(env.foreignId(), env.sender); + const auto &cb = [this, msgId=env.foreignId(), walletId] + (bs::sync::WalletData data) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_wallet_synced(); + msgResp->set_wallet_id(walletId); + msgResp->set_high_ext_index(data.highestExtIndex); + msgResp->set_high_int_index(data.highestIntIndex); + + for (const auto &addr : data.addresses) { + auto msgAddr = msgResp->add_addresses(); + msgAddr->set_index(addr.index); + msgAddr->set_address(addr.address.display()); + msgAddr->set_comment(addr.comment); + } + for (const auto &addr : data.addrPool) { + auto msgAddr = msgResp->add_addr_pool(); + msgAddr->set_index(addr.index); + msgAddr->set_address(addr.address.display()); + msgAddr->set_comment(addr.comment); + } + for (const auto &txCom : data.txComments) { + auto msgTxCom = msgResp->add_tx_comments(); + msgTxCom->set_tx_hash(txCom.txHash.toBinStr()); + msgTxCom->set_comment(txCom.comment); + } + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + signer_->syncWallet(walletId, cb); + return true; +} + +bool SignerAdapter::processSyncHdWallet(const bs::message::Envelope &env + , const std::string &walletId) +{ + requests_.put(env.foreignId(), env.sender); + const auto &cb = [this, msgId = env.foreignId(), walletId] + (bs::sync::HDWalletData data) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_hd_wallet_synced(); + *msgResp = data.toCommonMessage(); + msgResp->set_wallet_id(walletId); + + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + signer_->syncHDWallet(walletId, cb); + return true; +} + +bool SignerAdapter::processSyncAddrComment(const SignerMessage_SyncAddressComment &request) +{ + try { + signer_->syncAddressComment(request.wallet_id() + , bs::Address::fromAddressString(request.address()), request.comment()); + } + catch (const std::exception &) {} + return true; +} + +bool SignerAdapter::processSyncTxComment(const SignerMessage_SyncTxComment &request) +{ + signer_->syncTxComment(request.wallet_id() + , BinaryData::fromString(request.tx_hash()), request.comment()); + return true; +} + +bool SignerAdapter::processGetRootPubKey(const bs::message::Envelope &env + , const std::string &walletId) +{ + requests_.put(env.foreignId(), env.sender); + const auto &cb = [this, msgId=env.foreignId(), walletId] + (bool result, const SecureBinaryData &key) + { + auto sender = requests_.take(msgId); + if (!sender) { + return; + } + SignerMessage msg; + auto msgResp = msg.mutable_root_pubkey(); + msgResp->set_wallet_id(walletId); + msgResp->set_pub_key(key.toBinStr()); + msgResp->set_success(result); + + pushResponse(user_, sender, msg.SerializeAsString(), msgId); + }; + signer_->getRootPubkey(walletId, cb); + return true; +} + +bool SignerAdapter::processDelHdRoot(const std::string &walletId) +{ + return (signer_->DeleteHDRoot(walletId) > 0); +} + +bool SignerAdapter::processDelHdLeaf(const std::string &walletId) +{ + return (signer_->DeleteHDLeaf(walletId) > 0); +} + +bool SignerAdapter::processSignTx(const bs::message::Envelope& env + , const SignerMessage_SignTxRequest& request) +{ + const auto& cbSigned = [this, env, id=request.id()] + (BinaryData signedTX, bs::error::ErrorCode result, const std::string& errorReason) + { + passphrase_.clear(); + SignerMessage msg; + auto msgResp = msg.mutable_sign_tx_response(); + msgResp->set_id(id); + msgResp->set_signed_tx(signedTX.toBinStr()); + msgResp->set_error_code((int)result); + msgResp->set_error_text(errorReason); + pushResponse(user_, env, msg.SerializeAsString()); + }; + const auto& txReq = bs::signer::pbTxRequestToCore(request.tx_request(), logger_); + passphrase_ = SecureBinaryData::fromString(request.passphrase()); + signer_->signTXRequest(txReq, cbSigned + , static_cast(request.sign_mode()) + , request.keep_dup_recips()); + return true; +} + +bool SignerAdapter::processResolvePubSpenders(const bs::message::Envelope& env + , const bs::core::wallet::TXSignRequest& txReq) +{ + const auto& cbResolve = [this, env](bs::error::ErrorCode result + , const Codec_SignerState::SignerState& state) + { + SignerMessage msg; + auto msgResp = msg.mutable_resolved_spenders(); + msgResp->set_result((int)result); + msgResp->set_signer_state(state.SerializeAsString()); + pushResponse(user_, env, msg.SerializeAsString()); + }; + if (signer_->resolvePublicSpenders(txReq, cbResolve) == 0) { + logger_->error("[{}] failed to send", __func__); + } + return true; +} + +bool SignerAdapter::processAutoSignRequest(const bs::message::Envelope& env + , const SignerMessage_AutoSign& request) +{ + autoSignRequests_[request.wallet_id()] = env; + QVariantMap data; + data[QLatin1String("rootId")] = QString::fromStdString(request.wallet_id()); + data[QLatin1String("enable")] = request.enable(); + return (signer_->customDialogRequest(bs::signer::ui::GeneralDialogType::ActivateAutoSign + , data) != 0); +} + +bool SignerAdapter::processDialogRequest(const bs::message::Envelope& + , const SignerMessage_DialogRequest& request) +{ + const auto& dlgType = static_cast(request.dialog_type()); + QVariantMap data; + for (const auto& d : request.data()) { + data[QString::fromStdString(d.key())] = QString::fromStdString(d.value()); + } + return (signer_->customDialogRequest(dlgType, data) != 0); +} + +bool SignerAdapter::processCreateWallet(const bs::message::Envelope& env + , bool rescan, const SignerMessage_CreateWalletRequest& w) +{ + bs::wallet::PasswordData pwdData; + pwdData.password = SecureBinaryData::fromString(w.password()); + pwdData.metaData = { bs::wallet::EncryptionType::Password }; + //FIXME: pwdData.controlPassword = controlPassword(); + + SignerMessage msg; + auto msgResp = msg.mutable_created_wallet(); + try { + const auto& seed = w.xpriv_key().empty() ? bs::core::wallet::Seed(SecureBinaryData::fromString(w.seed()), netType_) + : bs::core::wallet::Seed::fromXpriv(SecureBinaryData::fromString(w.xpriv_key()), netType_); + + const auto& wallet = walletsMgr_->createWallet(w.name(), w.description(), seed + , walletsDir_, pwdData, w.primary()); + msgResp->set_wallet_id(wallet->walletId()); + walletsChanged(rescan); + logger_->debug("[{}] wallet {} created", __func__, wallet->walletId()); + } + catch (const std::exception& e) { + logger_->error("[{}] failed to create wallet: {}", __func__, e.what()); + msgResp->set_error_msg(e.what()); + } + pushResponse(user_, env, msg.SerializeAsString()); + return true; +} + +bool SignerAdapter::processDeleteWallet(const bs::message::Envelope& env + , const std::string& rootId) +{ + SignerMessage msg; + const auto& hdWallet = walletsMgr_->getHDWalletById(rootId); + if (hdWallet && walletsMgr_->deleteWalletFile(hdWallet)) { + msg.set_wallet_deleted(rootId); + } + else { + msg.set_wallet_deleted(""); + } + pushBroadcast(user_, msg.SerializeAsString()); + return true; +} diff --git a/Core/SignerAdapter.h b/Core/SignerAdapter.h new file mode 100644 index 000000000..1a942dc57 --- /dev/null +++ b/Core/SignerAdapter.h @@ -0,0 +1,127 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef SIGNER_ADAPTER_H +#define SIGNER_ADAPTER_H + +#include "BtcDefinitions.h" +#include "FutureValue.h" +#include "Wallets/HeadlessContainer.h" +#include "Message/Adapter.h" +#include "ThreadSafeContainers.h" + +namespace spdlog { + class logger; +} +namespace bs { + namespace core { + class WalletsManager; + } +} +namespace BlockSettle { + namespace Common { + class SignerMessage; + class SignerMessage_AutoSign; + class SignerMessage_CreateWalletRequest; + class SignerMessage_DialogRequest; + class SignerMessage_ExtendAddrChain; + class SignerMessage_GetSettlPayinAddr; + class SignerMessage_SetSettlementId; + class SignerMessage_SignSettlementTx; + class SignerMessage_SignTxRequest; + class SignerMessage_SyncAddresses; + class SignerMessage_SyncAddressComment; + class SignerMessage_SyncNewAddresses; + class SignerMessage_SyncTxComment; + } + namespace Terminal { + class SettingsMessage_SignerServer; + } +} +class SignerClient; +class WalletSignerContainer; + +class SignerAdapter : public bs::message::Adapter, public SignerCallbackTarget +{ +public: + SignerAdapter(const std::shared_ptr & + , const std::shared_ptr &signer = nullptr); + ~SignerAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "Signer"; } + + std::unique_ptr createClient() const; + +private: + void start(); + + // HCT overrides + void walletsChanged(bool rescan = false) override; + void onReady() override; + void walletsReady() override; + void newWalletPrompt() override; + void autoSignStateChanged(bs::error::ErrorCode + , const std::string& walletId) override; + + bool processOwnRequest(const bs::message::Envelope & + , const BlockSettle::Common::SignerMessage &); + bool processSignerSettings(const BlockSettle::Terminal::SettingsMessage_SignerServer &); + bool processNewKeyResponse(bool); + bool sendComponentLoading(); + + bool processStartWalletSync(const bs::message::Envelope &); + bool processSyncAddresses(const bs::message::Envelope & + , const BlockSettle::Common::SignerMessage_SyncAddresses &); + bool processSyncNewAddresses(const bs::message::Envelope & + , const BlockSettle::Common::SignerMessage_SyncNewAddresses &); + bool processExtendAddrChain(const bs::message::Envelope & + , const BlockSettle::Common::SignerMessage_ExtendAddrChain &); + bool processSyncWallet(const bs::message::Envelope &, const std::string &walletId); + bool processSyncHdWallet(const bs::message::Envelope &, const std::string &walletId); + bool processSyncAddrComment(const BlockSettle::Common::SignerMessage_SyncAddressComment &); + bool processSyncTxComment(const BlockSettle::Common::SignerMessage_SyncTxComment &); + bool processGetRootPubKey(const bs::message::Envelope &, const std::string &walletId); + bool processDelHdRoot(const std::string &walletId); + bool processDelHdLeaf(const std::string &walletId); + bool processSignTx(const bs::message::Envelope& + , const BlockSettle::Common::SignerMessage_SignTxRequest&); + bool processResolvePubSpenders(const bs::message::Envelope& + , const bs::core::wallet::TXSignRequest&); + bool processAutoSignRequest(const bs::message::Envelope& + , const BlockSettle::Common::SignerMessage_AutoSign&); + bool processDialogRequest(const bs::message::Envelope& + , const BlockSettle::Common::SignerMessage_DialogRequest&); + bool processCreateWallet(const bs::message::Envelope&, bool rescan + , const BlockSettle::Common::SignerMessage_CreateWalletRequest&); + bool processDeleteWallet(const bs::message::Envelope&, const std::string& rootId); + +private: + std::shared_ptr logger_; + std::shared_ptr user_; + NetworkType netType_{NetworkType::Invalid}; + std::string walletsDir_; + std::shared_ptr walletsMgr_; + std::shared_ptr signer_; + + std::shared_ptr> connFuture_; + std::string curServerId_; + std::string connKey_; + + bs::ThreadSafeMap> requests_; + std::unordered_map autoSignRequests_; + SecureBinaryData passphrase_; +}; + + +#endif // SIGNER_ADAPTER_H diff --git a/Core/TerminalMessage.cpp b/Core/TerminalMessage.cpp new file mode 100644 index 000000000..2a71896a7 --- /dev/null +++ b/Core/TerminalMessage.cpp @@ -0,0 +1,149 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include +#include "TerminalMessage.h" +#include "Message/Adapter.h" + +#include "common.pb.h" + +using namespace bs::message; +using namespace BlockSettle::Common; + +static const std::map kTerminalUsersMapping = { + { static_cast(TerminalUsers::BROADCAST), "Broadcast" }, + { static_cast(TerminalUsers::System), "System" }, + { static_cast(TerminalUsers::Signer), "Signer" }, + { static_cast(TerminalUsers::API), "API" }, + { static_cast(TerminalUsers::Settings), "Settings" }, + { static_cast(TerminalUsers::BsServer), "BsServer" }, + { static_cast(TerminalUsers::Matching), "Matching" }, + { static_cast(TerminalUsers::Assets), "Assets " }, + { static_cast(TerminalUsers::MktData), "MarketData" }, + { static_cast(TerminalUsers::MDHistory), "MDHistory" }, + { static_cast(TerminalUsers::Blockchain), "Blockchain" }, + { static_cast(TerminalUsers::Wallets), "Wallets" }, + { static_cast(TerminalUsers::OnChainTracker), "OnChainTrk" }, + { static_cast(TerminalUsers::Settlement), "Settlement" }, + { static_cast(TerminalUsers::Chat), "Chat" } +}; +static const std::string kMainQueue = "Main"; + +std::string UserTerminal::name() const +{ + const auto itAcc = kTerminalUsersMapping.find(value()); + return (itAcc == kTerminalUsersMapping.end()) + ? User::name() : itAcc->second; +} + + +TerminalInprocBus::TerminalInprocBus(const std::shared_ptr &logger) + : logger_(logger) +{ + queues_[kMainQueue] = std::make_shared(std::make_shared(logger) + , logger, kMainQueue); +} + +TerminalInprocBus::~TerminalInprocBus() +{ + shutdown(); +} + +void TerminalInprocBus::addAdapter(const std::shared_ptr &adapter) +{ + const auto& queue = queues_.at(kMainQueue); + queue->bindAdapter(adapter); + adapter->setQueue(queue); + const auto &runner = std::dynamic_pointer_cast(adapter); + if (runner) { + logger_->info("[{}] set runnable adapter {}", __func__, adapter->name()); + runnableAdapter_ = runner; + } + + const auto& relay = std::dynamic_pointer_cast(adapter); + if (relay) { + logger_->info("[{}] set relay adapter {}", __func__, adapter->name()); + relayAdapter_ = relay; + } + + sendLoading(adapter, queue); +} + +void bs::message::TerminalInprocBus::addAdapterWithQueue(const std::shared_ptr& adapter + , const std::string& qName) +{ + if (qName == kMainQueue) { + throw std::runtime_error("main queue name reused"); + } + std::shared_ptr queue; + const auto& itQueue = queues_.find(qName); + if (itQueue == queues_.end()) { + queue = std::make_shared(std::make_shared(logger_) + , logger_, qName); + queues_[qName] = queue; + } + else { + queue = itQueue->second; + } + + queue->bindAdapter(adapter); + adapter->setQueue(queue); + + if (relayAdapter_) { + queue->bindAdapter(relayAdapter_); + relayAdapter_->setQueue(queue); + logger_->debug("[{}] relay {} bound to {}", __func__, relayAdapter_->name(), qName); + } + else { + logger_->warn("[{}] no relay adapter attached to {}", __func__, qName); + } + sendLoading(adapter, queues_.at(kMainQueue)); +} + +void TerminalInprocBus::start() +{ + logger_->debug("[TerminalInprocBus::start]"); + static const auto &adminUser = UserTerminal::create(TerminalUsers::System); + AdministrativeMessage msg; + msg.mutable_start(); + auto env = bs::message::Envelope::makeBroadcast(adminUser, msg.SerializeAsString()); + queues_.at(kMainQueue)->pushFill(env); +} + +void bs::message::TerminalInprocBus::sendLoading(const std::shared_ptr& adapter + , const std::shared_ptr& queue) +{ + static const auto& adminUser = UserTerminal::create(TerminalUsers::System); + for (const auto& user : adapter->supportedReceivers()) { + AdministrativeMessage msg; + msg.set_component_created(user->value()); + auto env = bs::message::Envelope::makeBroadcast(adminUser, msg.SerializeAsString()); + queue->pushFill(env); + } +} + +void TerminalInprocBus::shutdown() +{ + runnableAdapter_.reset(); + relayAdapter_.reset(); + for (const auto& q : queues_) { + q.second->terminate(); + } +} + +bool TerminalInprocBus::run(int &argc, char **argv) +{ + start(); + if (!runnableAdapter_) { + return false; + } + runnableAdapter_->run(argc, argv); + return true; +} diff --git a/Core/TerminalMessage.h b/Core/TerminalMessage.h new file mode 100644 index 000000000..206c373cf --- /dev/null +++ b/Core/TerminalMessage.h @@ -0,0 +1,109 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef TERMINAL_MESSAGE_H +#define TERMINAL_MESSAGE_H + +#include "Message/Bus.h" +#include "Message/Envelope.h" + +namespace BlockSettle { + namespace Terminal { + class MatchingMessage_Quote; + class MatchingMessage_Order; + } +} + +namespace bs { + + class MainLoopRuner + { + public: + virtual void run(int &argc, char **argv) = 0; + }; + + namespace message { + enum class TerminalUsers : UserValue + { + Unknown = 0, + BROADCAST, + System, // Used only as a sender of administrative messages, no adapter registers it + Signer, // TX signing and core wallets loading + API, // API adapter and bus for API clients + Settings, // All kinds of static data + BsServer, // General target/origin of all sorts of BS server messages + Matching, // Alias for Celer (or other matching system in the future) + Assets, // Assets manipulation + MktData, // Market data retranslation + MDHistory, // Charts data storage + Blockchain, // General name for Armory connection + Wallets, // Sync wallet routines (loading, balance, UTXO reservation) + OnChainTracker,// Auth & CC tracker combined in one adapter + Settlement, // All settlements (FX, XBT, CC) for both dealer and requester + Chat, // Chat network routines + UsersCount + }; + + class UserTerminal : public User + { + public: + UserTerminal(TerminalUsers value) : User(static_cast(value)) {} + + std::string name() const override; + + bool isBroadcast() const override + { + return (static_cast(value()) == TerminalUsers::BROADCAST); + } + + bool isFallback() const override + { + return (static_cast(value()) == TerminalUsers::Unknown); + } + + bool isSystem() const override + { + return (static_cast(value()) == TerminalUsers::System); + } + + static std::shared_ptr create(TerminalUsers value) + { + return std::make_shared(value); + } + }; + + class TerminalInprocBus : public Bus + { + public: + TerminalInprocBus(const std::shared_ptr &); + ~TerminalInprocBus() override; + + void addAdapter(const std::shared_ptr &) override; + void addAdapterWithQueue(const std::shared_ptr& + , const std::string& qName); + + void shutdown(); + bool run(int &argc, char **argv); + + private: + void start(); + void sendLoading(const std::shared_ptr&, const std::shared_ptr&); + + private: + std::shared_ptr logger_; + std::map> queues_; + std::shared_ptr runnableAdapter_; + std::shared_ptr relayAdapter_; + }; + + } // namespace message +} // namespace bs + +#endif // TERMINAL_MESSAGE_H diff --git a/Core/unused/ChatAdapter.cpp b/Core/unused/ChatAdapter.cpp new file mode 100644 index 000000000..3499cc10e --- /dev/null +++ b/Core/unused/ChatAdapter.cpp @@ -0,0 +1,48 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "ChatAdapter.h" +#include +#include "TerminalMessage.h" + +#include "common.pb.h" + +using namespace BlockSettle::Common; +using namespace bs::message; + + +ChatAdapter::ChatAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(bs::message::TerminalUsers::Chat)) +{} + +bool ChatAdapter::process(const bs::message::Envelope &env) +{ + return true; +} + +bool ChatAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}", __func__, env.id()); + return false; + } + if (msg.data_case() == AdministrativeMessage::kStart) { + AdministrativeMessage admMsg; + admMsg.set_component_loading(user_->value()); + pushBroadcast(UserTerminal::create(TerminalUsers::System) + , admMsg.SerializeAsString()); + return true; + } + } + return false; +} diff --git a/Core/unused/ChatAdapter.h b/Core/unused/ChatAdapter.h new file mode 100644 index 000000000..e3cb113fd --- /dev/null +++ b/Core/unused/ChatAdapter.h @@ -0,0 +1,42 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef CHAT_ADAPTER_H +#define CHAT_ADAPTER_H + +#include "Message/Adapter.h" + +namespace spdlog { + class logger; +} + +class ChatAdapter : public bs::message::Adapter +{ +public: + ChatAdapter(const std::shared_ptr &); + ~ChatAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + std::set> supportedReceivers() const override { + return { user_ }; + } + std::string name() const override { return "Chat"; } + +private: + +private: + std::shared_ptr logger_; + std::shared_ptr user_; +}; + + +#endif // CHAT_ADAPTER_H diff --git a/Core/unused/MatchingAdapter.cpp b/Core/unused/MatchingAdapter.cpp new file mode 100644 index 000000000..4375488d5 --- /dev/null +++ b/Core/unused/MatchingAdapter.cpp @@ -0,0 +1,740 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "MatchingAdapter.h" +#include +#include "Celer/CommonUtils.h" +#include "Celer/CancelQuoteNotifSequence.h" +#include "Celer/CancelRFQSequence.h" +#include "Celer/CelerClientProxy.h" +#include "Celer/CreateOrderSequence.h" +#include "Celer/CreateFxOrderSequence.h" +#include "Celer/GetAssignedAccountsListSequence.h" +#include "Celer/SubmitQuoteNotifSequence.h" +#include "Celer/SubmitRFQSequence.h" +#include "CurrencyPair.h" +#include "MessageUtils.h" +#include "ProtobufUtils.h" +#include "TerminalMessage.h" + +#include "terminal.pb.h" +#include "DownstreamQuoteProto.pb.h" +#include "DownstreamOrderProto.pb.h" +#include "bitcoin/DownstreamBitcoinTransactionSigningProto.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; +using namespace com::celertech::marketmerchant::api::enums::orderstatus; +using namespace com::celertech::marketmerchant::api::enums::producttype::quotenotificationtype; +using namespace com::celertech::marketmerchant::api::enums::side; +using namespace com::celertech::marketmerchant::api::order; +using namespace com::celertech::marketmerchant::api::quote; + + +MatchingAdapter::MatchingAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(TerminalUsers::Matching)) + , userSettl_(std::make_shared(TerminalUsers::Settlement)) + , userWallets_(std::make_shared(TerminalUsers::Wallets)) +{ + celerConnection_ = std::make_unique(logger, this, true, true); + + celerConnection_->RegisterHandler(CelerAPI::QuoteDownstreamEventType + , [this](const std::string& data) { + return onQuoteResponse(data); + }); + celerConnection_->RegisterHandler(CelerAPI::QuoteRequestRejectDownstreamEventType + , [this](const std::string& data) { + return onQuoteReject(data); + }); + celerConnection_->RegisterHandler(CelerAPI::CreateOrderRequestRejectDownstreamEventType + , [this](const std::string& data) { + return onOrderReject(data); + }); + celerConnection_->RegisterHandler(CelerAPI::BitcoinOrderSnapshotDownstreamEventType + , [this](const std::string& data) { + return onBitcoinOrderSnapshot(data); + }); + celerConnection_->RegisterHandler(CelerAPI::FxOrderSnapshotDownstreamEventType + , [this](const std::string& data) { + return onFxOrderSnapshot(data); + }); + celerConnection_->RegisterHandler(CelerAPI::QuoteCancelDownstreamEventType + , [this](const std::string& data) { + return onQuoteCancelled(data); + }); + celerConnection_->RegisterHandler(CelerAPI::SignTransactionNotificationType + , [this](const std::string& data) { + return onSignTxNotif(data); + }); + celerConnection_->RegisterHandler(CelerAPI::QuoteAckDownstreamEventType + , [this](const std::string& data) { + return onQuoteAck(data); + }); + celerConnection_->RegisterHandler(CelerAPI::QuoteRequestNotificationType + , [this](const std::string& data) { + return onQuoteReqNotification(data); + }); + celerConnection_->RegisterHandler(CelerAPI::QuoteCancelNotifReplyType + , [this](const std::string& data) { + return onQuoteNotifCancelled(data); + }); + celerConnection_->RegisterHandler(CelerAPI::SubLedgerSnapshotDownstreamEventType + , [](const std::string&) { return true; }); // remove warning from log +} + +void MatchingAdapter::connectedToServer() +{ + logger_->debug("[MatchingAdapter::connectedToServer]"); + MatchingMessage msg; + auto loggedIn = msg.mutable_logged_in(); + loggedIn->set_user_type(static_cast(celerConnection_->celerUserType())); + loggedIn->set_user_id(celerConnection_->userId()); + loggedIn->set_user_name(celerConnection_->userName()); + pushBroadcast(user_, msg.SerializeAsString()); + + sendSetUserId(celerConnection_->userId()); + + const auto &cbAccounts = [this](const std::vector& accVec) + { // Remove duplicated entries if possible + std::set accounts(accVec.cbegin(), accVec.cend()); + if (accounts.size() == 1) { + assignedAccount_ = *accounts.cbegin(); + logger_->debug("[MatchingAdapter] assigned account: {}", assignedAccount_); + } + else if (accounts.empty()) { + logger_->error("[MatchingAdapter::onCelerConnected] no accounts"); + } else { + logger_->error("[MatchingAdapter::onCelerConnected] too many accounts ({})" + , accounts.size()); + for (const auto& account : accounts) { + logger_->error("[MatchingAdapter::onCelerConnected] acc: {}", account); + } + } + }; + if (!celerConnection_->ExecuteSequence(std::make_shared( + logger_, cbAccounts))) { + logger_->error("[{}] failed to get accounts", __func__); + } +} + +void MatchingAdapter::connectionClosed() +{ + sendSetUserId({}); + + assignedAccount_.clear(); + celerConnection_->CloseConnection(); + + MatchingMessage msg; + msg.mutable_logged_out(); + pushBroadcast(user_, msg.SerializeAsString()); +} + +void MatchingAdapter::connectionError(int errorCode) +{ + logger_->debug("[MatchingAdapter::connectionError] {}", errorCode); + MatchingMessage msg; + msg.set_connection_error(errorCode); + pushBroadcast(user_, msg.SerializeAsString()); +} + +bool MatchingAdapter::process(const bs::message::Envelope &env) +{ + if (!env.isRequest() && (env.sender->value() == bs::message::TerminalUsers::BsServer)) { + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse BsServer message #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case BsServerMessage::kRecvMatching: + celerConnection_->recvData(static_cast(msg.recv_matching().message_type()) + , msg.recv_matching().data()); + break; + default: break; + } + } + else if (env.receiver->value() == user_->value()) { + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse message #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case MatchingMessage::kLogin: + return processLogin(msg.login()); + case MatchingMessage::kLogout: + if (celerConnection_ && celerConnection_->IsConnected()) { + celerConnection_->CloseConnection(); + } + break; + case MatchingMessage::kGetSubmittedAuthAddresses: + return processGetSubmittedAuth(env); + case MatchingMessage::kSubmitAuthAddress: + return processSubmitAuth(env, msg.submit_auth_address()); + case MatchingMessage::kSendRfq: + return processSendRFQ(msg.send_rfq()); + case MatchingMessage::kAcceptRfq: + return processAcceptRFQ(msg.accept_rfq()); + case MatchingMessage::kCancelRfq: + return processCancelRFQ(msg.cancel_rfq()); + case MatchingMessage::kSubmitQuoteNotif: + return processSubmitQuote(msg.submit_quote_notif()); + case MatchingMessage::kPullQuoteNotif: + return processPullQuote(msg.pull_quote_notif()); + default: + logger_->warn("[MatchingAdapter::process] unknown msg {} #{} from {}" + , msg.data_case(), env.id(), env.sender->name()); + break; + } + } + return true; +} + +bool MatchingAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}", __func__, env.id()); + return false; + } + if (msg.data_case() == AdministrativeMessage::kStart) { + AdministrativeMessage admMsg; + admMsg.set_component_loading(user_->value()); + pushBroadcast(UserTerminal::create(TerminalUsers::System) + , admMsg.SerializeAsString()); + return true; + } + } + return false; +} + +bool MatchingAdapter::processLogin(const MatchingMessage_Login& request) +{ + return celerConnection_->SendLogin(request.matching_login() + , request.terminal_login(), {}); +} + +bool MatchingAdapter::processGetSubmittedAuth(const bs::message::Envelope& env) +{ + MatchingMessage msg; + auto msgResp = msg.mutable_submitted_auth_addresses(); + for (const auto& addr : celerConnection_->GetSubmittedAuthAddressSet()) { + msgResp->add_addresses(addr); + } + return pushResponse(user_, env, msg.SerializeAsString()); +} + +bool MatchingAdapter::processSubmitAuth(const bs::message::Envelope& env + , const std::string& address) +{ + auto submittedAddresses = celerConnection_->GetSubmittedAuthAddressSet(); + submittedAddresses.insert(address); + celerConnection_->SetSubmittedAuthAddressSet(submittedAddresses); + return processGetSubmittedAuth(env); +} + +bool MatchingAdapter::processSendRFQ(const BlockSettle::Terminal::RFQ& request) +{ + if (assignedAccount_.empty()) { + logger_->error("[MatchingAdapter::processSendRFQ] submitting with empty account name"); + } + bs::network::RFQ rfq; + rfq.requestId = request.id(); + rfq.security = request.security(); + rfq.product = request.product(); + rfq.assetType = static_cast(request.asset_type()); + rfq.side = request.buy() ? bs::network::Side::Buy : bs::network::Side::Sell; + rfq.quantity = request.quantity(); + rfq.requestorAuthPublicKey = request.auth_pub_key(); + rfq.receiptAddress = request.receipt_address(); + rfq.coinTxInput = request.coin_tx_input(); + auto sequence = std::make_shared(assignedAccount_, rfq + , logger_, true); + if (!celerConnection_->ExecuteSequence(sequence)) { + logger_->error("[MatchingAdapter::processSendRFQ] failed to execute CelerSubmitRFQSequence"); + } else { + logger_->debug("[MatchingAdapter::processSendRFQ] RFQ submitted: {}", rfq.requestId); + submittedRFQs_[rfq.requestId] = rfq; + } + return true; +} + +bool MatchingAdapter::processAcceptRFQ(const AcceptRFQ& request) +{ + if (assignedAccount_.empty()) { + logger_->error("[MatchingAdapter::processAcceptRFQ] accepting with empty account name"); + } + const auto& reqId = QString::fromStdString(request.rfq_id()); + const auto& quote = fromMsg(request.quote()); + if (quote.assetType == bs::network::Asset::SpotFX) { + auto sequence = std::make_shared(assignedAccount_ + , reqId, quote, logger_); + if (!celerConnection_->ExecuteSequence(sequence)) { + logger_->error("[MatchingAdapter::processAcceptRFQ] failed to execute CelerCreateFxOrderSequence"); + } else { + logger_->debug("[MatchingAdapter::processAcceptRFQ] FX Order submitted"); + } + } + else { + auto sequence = std::make_shared(assignedAccount_ + , reqId, quote, request.payout_tx(), logger_); + if (!celerConnection_->ExecuteSequence(sequence)) { + logger_->error("[MatchingAdapter::processAcceptRFQ] failed to execute CelerCreateOrderSequence"); + } else { + logger_->debug("[MatchingAdapter::processAcceptRFQ] Order submitted"); + } + } + return true; +} + +bool MatchingAdapter::processCancelRFQ(const std::string& rfqId) +{ + const auto &sequence = std::make_shared( + QString::fromStdString(rfqId), logger_); + if (!celerConnection_->ExecuteSequence(sequence)) { + logger_->error("[MatchingAdapter::processCancelRFQ] failed to execute CelerCancelRFQSequence"); + return false; + } else { + logger_->debug("[MatchingAdapter::processCancelRFQ] RFQ {} cancelled", rfqId); + } + return true; +} + +bool MatchingAdapter::processSubmitQuote(const ReplyToRFQ& request) +{ + if (assignedAccount_.empty()) { + logger_->warn("[MatchingAdapter::processSubmitQuote] account name not set"); + } + const auto& qn = fromMsg(request); + const auto &sequence = std::make_shared(assignedAccount_, qn, logger_); + if (!celerConnection_->ExecuteSequence(sequence)) { + logger_->error("[MatchingAdapter::processSubmitQuote] failed to execute CelerSubmitQuoteNotifSequence"); + } else { + logger_->debug("[MatchingAdapter::processSubmitQuote] QuoteNotification on {} submitted" + , qn.quoteRequestId); + } + return true; +} + +bool MatchingAdapter::processPullQuote(const PullRFQReply& request) +{ + std::shared_ptr sequence; + try { + sequence = std::make_shared(request.rfq_id() + , request.session_token(), logger_); + } + catch (const std::exception& e) { + logger_->error("[{}] failed to init: {}", __func__, e.what()); + return true; + } + if (!celerConnection_->ExecuteSequence(sequence)) { + logger_->error("[MatchingAdapter::processPullQuote] failed to execute CancelQuoteNotifSequence"); + } else { + logger_->debug("[MatchingAdapter::processPullQuote] QuoteNotification on {} pulled" + , request.rfq_id()); + } + return true; +} + +void MatchingAdapter::saveQuoteRequestCcy(const std::string& id, const std::string& ccy) +{ + quoteCcys_.emplace(id, ccy); +} + +void MatchingAdapter::cleanQuoteRequestCcy(const std::string& id) +{ + auto it = quoteCcys_.find(id); + if (it != quoteCcys_.end()) { + quoteCcys_.erase(it); + } +} + +void MatchingAdapter::sendSetUserId(const std::string& userId) +{ + logger_->debug("[{}] setting userId {}", __func__, userId); + WalletsMessage msg; + msg.set_set_user_id(celerConnection_->userId()); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +std::string MatchingAdapter::getQuoteRequestCcy(const std::string& id) const +{ + std::string ccy; + auto it = quoteCcys_.find(id); + if (it != quoteCcys_.end()) { + ccy = it->second; + } + return ccy; +} + +bool MatchingAdapter::onQuoteResponse(const std::string& data) +{ + QuoteDownstreamEvent response; + + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onQuoteResponse] Failed to parse QuoteDownstreamEvent"); + return false; + } + logger_->debug("[MatchingAdapter::onQuoteResponse]: {}", ProtobufUtils::toJsonCompact(response)); + + bs::network::Quote quote; + quote.quoteId = response.quoteid(); + quote.requestId = response.quoterequestid(); + quote.security = response.securitycode(); + quote.assetType = bs::celer::fromCelerProductType(response.producttype()); + quote.side = bs::celer::fromCeler(response.side()); + + if (quote.assetType == bs::network::Asset::PrivateMarket) { + quote.dealerAuthPublicKey = response.dealerreceiptaddress(); + quote.dealerTransaction = response.dealercointransactioninput(); + } + + switch (response.quotingtype()) { + case com::celertech::marketmerchant::api::enums::quotingtype::AUTOMATIC: + quote.quotingType = bs::network::Quote::Automatic; + break; + case com::celertech::marketmerchant::api::enums::quotingtype::MANUAL: + quote.quotingType = bs::network::Quote::Manual; + break; + case com::celertech::marketmerchant::api::enums::quotingtype::DIRECT: + quote.quotingType = bs::network::Quote::Direct; + break; + case com::celertech::marketmerchant::api::enums::quotingtype::INDICATIVE: + quote.quotingType = bs::network::Quote::Indicative; + break; + case com::celertech::marketmerchant::api::enums::quotingtype::TRADEABLE: + quote.quotingType = bs::network::Quote::Tradeable; + break; + default: + quote.quotingType = bs::network::Quote::Indicative; + break; + } + + quote.expirationTime = QDateTime::fromMSecsSinceEpoch(response.validuntiltimeutcinmillis()); + quote.timeSkewMs = QDateTime::fromMSecsSinceEpoch(response.quotetimestamputcinmillis()).msecsTo(QDateTime::currentDateTime()); + quote.celerTimestamp = response.quotetimestamputcinmillis(); + + logger_->debug("[MatchingAdapter::onQuoteResponse] timeSkew = {}", quote.timeSkewMs); + CurrencyPair cp(quote.security); + + const auto& grp = response.legquotegroup(0); + quote.product = grp.currency(); + + const auto& itRFQ = submittedRFQs_.find(response.quoterequestid()); + if (itRFQ == submittedRFQs_.end()) { // Quote for dealer to indicate GBBO //WTF? + const auto quoteCcy = getQuoteRequestCcy(quote.requestId); + if (!quoteCcy.empty()) { + double price = 0; + + if ((quote.side == bs::network::Side::Sell) ^ (quoteCcy != cp.NumCurrency())) { + price = response.offerpx(); + } else { + price = response.bidpx(); + } + + const bool own = response.has_quotedbysessionkey() && !response.quotedbysessionkey().empty(); + MatchingMessage msg; + auto msgBest = msg.mutable_best_quoted_price(); + msgBest->set_quote_req_id(response.quoterequestid()); + msgBest->set_price(price); + msgBest->set_own(own); + pushRequest(user_, userSettl_, msg.SerializeAsString()); + } + quote.quantity = grp.bidsize(); // equal to offersize/offerpx regardless of side + quote.price = response.bidpx(); + } else { + if (response.legquotegroup_size() != 1) { + logger_->error("[MatchingAdapter::onQuoteResponse] invalid leg number: {}\n{}" + , response.legquotegroup_size() + , ProtobufUtils::toJsonCompact(response)); + return false; + } + + if (quote.assetType == bs::network::Asset::SpotXBT) { + quote.requestorAuthPublicKey = itRFQ->second.requestorAuthPublicKey; + } + + if ((quote.side == bs::network::Side::Sell) ^ (itRFQ->second.product != cp.NumCurrency())) { + quote.price = response.offerpx(); + quote.quantity = grp.offersize(); + } else { + quote.price = response.bidpx(); + quote.quantity = grp.bidsize(); + } + + if (quote.quotingType == bs::network::Quote::Tradeable) { + submittedRFQs_.erase(itRFQ); + } + } + + if (quote.assetType == bs::network::Asset::SpotXBT) { + quote.dealerAuthPublicKey = response.dealerauthenticationaddress(); + quote.dealerTransaction = response.dealertransaction(); + if (response.has_settlementid() && !response.settlementid().empty()) { + quote.settlementId = response.settlementid(); + } + } + MatchingMessage msg; + toMsg(quote, msg.mutable_quote()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onQuoteReject(const std::string& data) +{ + QuoteRequestRejectDownstreamEvent response; + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onQuoteReject] failed to parse"); + return false; + } + logger_->debug("[MatchingAdapter::onQuoteReject] {}", ProtobufUtils::toJsonCompact(response)); + + MatchingMessage msg; + auto msgReq = msg.mutable_quote_reject(); + msgReq->set_rfq_id(response.quoterequestid()); + if (response.quoterequestrejectgroup_size() > 0) { + const QuoteRequestRejectGroup& rejGrp = response.quoterequestrejectgroup(0); + msgReq->set_reject_text(rejGrp.text()); + } + msgReq->set_reject_code((int)response.quoterequestrejectreason()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onOrderReject(const std::string& data) +{ + CreateOrderRequestRejectDownstreamEvent response; + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onQuoteReject] failed to parse"); + return false; + } + logger_->debug("[MatchingAdapter::onOrderReject] {}", ProtobufUtils::toJsonCompact(response)); + + MatchingMessage msg; + auto msgReq = msg.mutable_order_reject(); + msgReq->set_order_id(response.externalclorderid()); + msgReq->set_quote_id(response.quoteid()); + msgReq->set_reject_text(response.rejectreason()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onBitcoinOrderSnapshot(const std::string& data) +{ + BitcoinOrderSnapshotDownstreamEvent response; + + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onBitcoinOrderSnapshot] failed to parse"); + return false; + } + logger_->debug("[MatchingAdapter::onBitcoinOrderSnapshot] {}", ProtobufUtils::toJsonCompact(response)); + + auto orderDate = QDateTime::fromMSecsSinceEpoch(response.createdtimestamputcinmillis()); + //auto ageSeconds = orderDate.secsTo(QDateTime::currentDateTime()); + + bs::network::Order order; + order.exchOrderId = QString::number(response.orderid()); + order.clOrderId = response.externalclorderid(); + order.quoteId = response.quoteid(); + order.dateTime = QDateTime::fromMSecsSinceEpoch(response.createdtimestamputcinmillis()); + order.security = response.securitycode(); + order.quantity = response.qty(); + order.price = response.price(); + order.product = response.currency(); + order.side = bs::celer::fromCeler(response.side()); + order.assetType = bs::celer::fromCelerProductType(response.producttype()); + try { + order.settlementId = BinaryData::CreateFromHex(response.settlementid()); + } + catch (const std::exception& e) { + logger_->error("[MatchingAdapter::onBitcoinOrderSnapshot] failed to parse settlement id"); + return false; + } + order.reqTransaction = response.requestortransaction(); + order.dealerTransaction = response.dealertransaction(); + order.status = bs::celer::mapBtcOrderStatus(response.orderstatus()); + order.pendingStatus = response.info(); + + MatchingMessage msg; + toMsg(order, msg.mutable_order()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onFxOrderSnapshot(const std::string& data) +{ + FxOrderSnapshotDownstreamEvent response; + if (!response.ParseFromString(data)) { + logger_->error("[QuoteProvider::onFxOrderSnapshot] Failed to parse FxOrderSnapshotDownstreamEvent"); + return false; + } + logger_->debug("[MatchingAdapter::onFxOrderSnapshot] {}", response.DebugString()); + + bs::network::Order order; + order.exchOrderId = QString::number(response.orderid()); + order.clOrderId = response.externalclorderid(); + order.quoteId = response.quoteid(); + order.dateTime = QDateTime::fromMSecsSinceEpoch(response.createdtimestamputcinmillis()); + order.security = response.securitycode(); + order.quantity = response.qty(); + order.leavesQty = response.leavesqty(); + order.price = response.price(); + order.avgPx = response.avgpx(); + order.product = response.currency(); + order.side = bs::celer::fromCeler(response.side()); + order.assetType = bs::network::Asset::SpotFX; + + order.status = bs::celer::mapFxOrderStatus(response.orderstatus()); + order.info = response.info(); + + MatchingMessage msg; + toMsg(order, msg.mutable_order()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onQuoteCancelled(const std::string& data) +{ + QuoteCancelDownstreamEvent response; + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onQuoteCancelled] failed to parse"); + return false; + } + logger_->debug("[MatchingAdapter::onQuoteCancelled] {}", ProtobufUtils::toJsonCompact(response)); + + MatchingMessage msg; + auto msgData = msg.mutable_quote_cancelled(); + msgData->set_rfq_id(response.quoterequestid()); + msgData->set_quote_id(response.quoteid()); + msgData->set_by_user(response.quotecanceltype() == + com::celertech::marketmerchant::api::enums::quotecanceltype::CANCEL_ALL_QUOTES); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onSignTxNotif(const std::string&) +{ + return false; +} + +bool MatchingAdapter::onQuoteAck(const std::string&) +{ + return false; +} + +bool MatchingAdapter::onQuoteReqNotification(const std::string& data) +{ + QuoteRequestNotification response; + + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onQuoteReqNotification] failed to parse"); + return false; + } + + if (response.quoterequestnotificationgroup_size() < 1) { + logger_->error("[MatchingAdapter::onQuoteReqNotification] missing at least 1 QRN group"); + return false; + } // For SpotFX and SpotXBT there should be only 1 group + + const QuoteRequestNotificationGroup& respgrp = response.quoterequestnotificationgroup(0); + + if (respgrp.quoterequestnotificationleggroup_size() != 1) { + logger_->error("[MatchingAdapter::onQuoteReqNotification] wrong leg group size: {}\n{}" + , respgrp.quoterequestnotificationleggroup_size() + , ProtobufUtils::toJsonCompact(response)); + return false; + } + + const auto& legGroup = respgrp.quoterequestnotificationleggroup(0); + + bs::network::QuoteReqNotification qrn; + qrn.quoteRequestId = response.quoterequestid(); + qrn.security = respgrp.securitycode(); + qrn.sessionToken = response.requestorsessiontoken(); + qrn.quantity = legGroup.qty(); + qrn.product = respgrp.currency(); + qrn.party = respgrp.partyid(); + //qrn.reason = response.reason(); + //qrn.account = response.account(); + qrn.expirationTime = response.expiretimeinutcinmillis(); + qrn.timestamp = response.timestampinutcinmillis(); + qrn.timeSkewMs = QDateTime::fromMSecsSinceEpoch(qrn.timestamp).msecsTo(QDateTime::currentDateTime()); + + qrn.side = bs::celer::fromCeler(legGroup.side()); + qrn.assetType = bs::celer::fromCelerProductType(respgrp.producttype()); + + switch (response.quotenotificationtype()) { + case QUOTE_WITHDRAWN: + qrn.status = bs::network::QuoteReqNotification::Withdrawn; + break; + case PENDING_ACKNOWLEDGE: + qrn.status = bs::network::QuoteReqNotification::PendingAck; + break; + default: + qrn.status = bs::network::QuoteReqNotification::StatusUndefined; + break; + } + + if (response.has_settlementid() && !response.settlementid().empty()) { + qrn.settlementId = response.settlementid(); + } + + switch (qrn.assetType) { + case bs::network::Asset::SpotXBT: + qrn.requestorAuthPublicKey = response.requestorauthenticationaddress(); + break; + case bs::network::Asset::PrivateMarket: + qrn.requestorAuthPublicKey = respgrp.requestorcointransactioninput(); + qrn.requestorRecvAddress = response.requestorreceiptaddress(); + break; + default: break; + } + + saveQuoteRequestCcy(qrn.quoteRequestId, qrn.product); + + logger_->debug("[MatchingAdapter::onQuoteReqNotification] {}", ProtobufUtils::toJsonCompact(response)); + MatchingMessage msg; + toMsg(qrn, msg.mutable_incoming_rfq()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingAdapter::onQuoteNotifCancelled(const std::string& data) +{ + QuoteCancelDownstreamEvent response; + if (!response.ParseFromString(data)) { + logger_->error("[MatchingAdapter::onQuoteNotifCancelled] failed to parse"); + return false; + } + logger_->debug("[MatchingAdapter::onQuoteNotifCancelled] {}", ProtobufUtils::toJsonCompact(response)); + + MatchingMessage msg; + auto msgReq = msg.mutable_quote_cancelled(); + msgReq->set_rfq_id(response.quoterequestid()); + if (response.quotecanceltype() != com::celertech::marketmerchant::api::enums::quotecanceltype::CANCEL_ALL_QUOTES) { + msgReq->set_quote_id(response.quoteid()); + } + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + + +ClientCelerConnection::ClientCelerConnection(const std::shared_ptr& logger + , MatchingAdapter* parent, bool userIdRequired, bool useRecvTimer) + : BaseCelerClient(logger, parent, userIdRequired, useRecvTimer) + , parent_(parent) +{} + +void ClientCelerConnection::onSendData(CelerAPI::CelerMessageType messageType + , const std::string& data) +{ + BsServerMessage msg; + auto msgReq = msg.mutable_send_matching(); + msgReq->set_message_type((int)messageType); + msgReq->set_data(data); + auto env = Envelope::makeRequest(parent_->user_, UserTerminal::create(TerminalUsers::BsServer) + , msg.SerializeAsString()); + parent_->pushFill(env); +} diff --git a/Core/unused/MatchingAdapter.h b/Core/unused/MatchingAdapter.h new file mode 100644 index 000000000..001d632e4 --- /dev/null +++ b/Core/unused/MatchingAdapter.h @@ -0,0 +1,105 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef MATCHING_ADAPTER_H +#define MATCHING_ADAPTER_H + +#include "Celer/BaseCelerClient.h" +#include "Message/Adapter.h" + +namespace spdlog { + class logger; +} +namespace BlockSettle { + namespace Terminal { + class AcceptRFQ; + class MatchingMessage_Login; + class PullRFQReply; + class RFQ; + class ReplyToRFQ; + } +} + +class MatchingAdapter; +class ClientCelerConnection : public BaseCelerClient +{ +public: + ClientCelerConnection(const std::shared_ptr& logger + , MatchingAdapter* parent, bool userIdRequired, bool useRecvTimer); + ~ClientCelerConnection() noexcept override = default; + +protected: + void onSendData(CelerAPI::CelerMessageType messageType, const std::string& data) override; + +private: + MatchingAdapter* parent_{ nullptr }; +}; + + +class MatchingAdapter : public bs::message::Adapter, public CelerCallbackTarget +{ + friend class ClientCelerConnection; +public: + MatchingAdapter(const std::shared_ptr &); + ~MatchingAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + std::set> supportedReceivers() const override { + return { user_ }; + } + std::string name() const override { return "Matching"; } + +private: + // CelerCallbackTarget overrides + void connectedToServer() override; + void connectionClosed() override; + void connectionError(int errorCode) override; + + bool processLogin(const BlockSettle::Terminal::MatchingMessage_Login&); + bool processGetSubmittedAuth(const bs::message::Envelope&); + bool processSubmitAuth(const bs::message::Envelope&, const std::string& address); + bool processSendRFQ(const BlockSettle::Terminal::RFQ&); + bool processAcceptRFQ(const BlockSettle::Terminal::AcceptRFQ&); + bool processCancelRFQ(const std::string& rfqId); + bool processSubmitQuote(const BlockSettle::Terminal::ReplyToRFQ&); + bool processPullQuote(const BlockSettle::Terminal::PullRFQReply&); + + std::string getQuoteRequestCcy(const std::string& id) const; + void saveQuoteRequestCcy(const std::string& id, const std::string& ccy); + void cleanQuoteRequestCcy(const std::string& id); + + void sendSetUserId(const std::string &userId); + + bool onQuoteResponse(const std::string&); + bool onQuoteReject(const std::string&); + bool onOrderReject(const std::string&); + bool onBitcoinOrderSnapshot(const std::string&); + bool onFxOrderSnapshot(const std::string&); + bool onQuoteCancelled(const std::string&); + bool onSignTxNotif(const std::string&); + bool onQuoteAck(const std::string&); + bool onQuoteReqNotification(const std::string&); + bool onQuoteNotifCancelled(const std::string&); + +private: + std::shared_ptr logger_; + std::shared_ptr user_, userSettl_, userWallets_; + std::unique_ptr celerConnection_; + + std::string assignedAccount_; + std::unordered_map submittedRFQs_; + std::unordered_map> quoteIds_; + std::unordered_map quoteCcys_; +}; + + +#endif // MATCHING_ADAPTER_H diff --git a/Core/unused/SettlementAdapter.cpp b/Core/unused/SettlementAdapter.cpp new file mode 100644 index 000000000..b30a4b60e --- /dev/null +++ b/Core/unused/SettlementAdapter.cpp @@ -0,0 +1,938 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "SettlementAdapter.h" +#include +#include "BSErrorCode.h" +#include "CurrencyPair.h" +#include "MessageUtils.h" +#include "ProtobufHeadlessUtils.h" +#include "TerminalMessage.h" +#include "UiUtils.h" // only for actualXbtPrice() and displayPriceXBT() + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + +constexpr auto kHandshakeTimeout = std::chrono::seconds{ 30 }; + + +SettlementAdapter::SettlementAdapter(const std::shared_ptr &logger) + : logger_(logger) + , user_(std::make_shared(TerminalUsers::Settlement)) + , userBS_(std::make_shared(TerminalUsers::BsServer)) + , userMtch_(std::make_shared(TerminalUsers::Matching)) + , userWallets_(std::make_shared(TerminalUsers::Wallets)) + , userSigner_(std::make_shared(TerminalUsers::Signer)) +{} + +bool SettlementAdapter::process(const bs::message::Envelope &env) +{ + if (env.sender->value() == userMtch_->value()) { + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse matching msg #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case MatchingMessage::kQuote: + return processMatchingQuote(msg.quote()); + case MatchingMessage::kOrder: + return processMatchingOrder(msg.order()); + case MatchingMessage::kIncomingRfq: + return processMatchingInRFQ(msg.incoming_rfq()); + default: break; + } + if (!env.receiver || env.receiver->isBroadcast()) { + return false; + } + } + else if (env.sender->value() == userBS_->value()) { + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse BS msg #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case BsServerMessage::kUnsignedPayinRequested: + return processBsUnsignedPayin(BinaryData::fromString(msg.unsigned_payin_requested())); + case BsServerMessage::kSignedPayinRequested: + return processBsSignPayin(msg.signed_payin_requested()); + case BsServerMessage::kSignedPayoutRequested: + return processBsSignPayout(msg.signed_payout_requested()); + default: break; + } + if (!env.receiver || env.receiver->isBroadcast()) { + return false; + } + } + else if (env.sender->value() == userWallets_->value()) { + WalletsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse wallets msg #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case WalletsMessage::kXbtTxResponse: + return processXbtTx(env.responseId(), msg.xbt_tx_response()); + default: break; + } + if (!env.receiver || env.receiver->isBroadcast()) { + return false; + } + } + else if (env.sender->value() == userSigner_->value()) { + SignerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse signer msg #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case SignerMessage::kSignTxResponse: + return processSignedTx(env.responseId(), msg.sign_tx_response()); + default: break; + } + if (!env.receiver || env.receiver->isBroadcast()) { + return false; + } + } + else if (env.receiver->value() == user_->value()) { + SettlementMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own request #{}", __func__, env.id()); + return true; + } + switch (msg.data_case()) { + case SettlementMessage::kCancelRfq: + return processCancelRFQ(msg.cancel_rfq()); + case SettlementMessage::kAcceptRfq: + return processAcceptRFQ(env, msg.accept_rfq()); + case SettlementMessage::kSendRfq: + return processSendRFQ(env, msg.send_rfq()); + case SettlementMessage::kHandshakeTimeout: + return processHandshakeTimeout(msg.handshake_timeout()); + case SettlementMessage::kQuoteReqTimeout: + return processInRFQTimeout(msg.quote_req_timeout()); + case SettlementMessage::kReplyToRfq: + return processSubmitQuote(env, msg.reply_to_rfq()); + case SettlementMessage::kPullRfqReply: + return processPullQuote(env, msg.pull_rfq_reply()); + default: + logger_->warn("[{}] unknown settlement request {}", __func__, msg.data_case()); + break; + } + } + return true; +} + +bool SettlementAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (env.sender->isSystem()) { + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse administrative message #{}" + , __func__, env.id()); + return false; + } + if (msg.data_case() == AdministrativeMessage::kStart) { + AdministrativeMessage admMsg; + admMsg.set_component_loading(user_->value()); + pushBroadcast(UserTerminal::create(TerminalUsers::System) + , admMsg.SerializeAsString()); + return true; + } + } + else if (env.sender->value() == TerminalUsers::Blockchain) { + ArmoryMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse armory msg #{}", __func__, env.id()); + return false; + } + switch (msg.data_case()) { + case ArmoryMessage::kZcReceived: + processZC(msg.zc_received()); + return true; + default: break; + } + } + return false; +} + +bool SettlementAdapter::processZC(const ArmoryMessage_ZCReceived& zcData) +{ + for (const auto& zcEntry : zcData.tx_entries()) { + const auto& txHash = BinaryData::fromString(zcEntry.tx_hash()); + const auto& itZC = pendingZCs_.find(txHash); + if (itZC == pendingZCs_.end()) { + continue; + } + const auto settlementId = itZC->second; + pendingZCs_.erase(itZC); + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl == settlBySettlId_.end()) { + logger_->warn("[{}] settlement not found for {}", __func__, settlementId.toHexStr()); + continue; + } + SettlementMessage msg; + auto msgResponse = msg.mutable_settlement_complete(); + msgResponse->set_rfq_id(itSettl->second->rfq.requestId); + msgResponse->set_quote_id(itSettl->second->quote.quoteId); + msgResponse->set_settlement_id(settlementId.toBinStr()); + pushResponse(user_, itSettl->second->env.sender, msg.SerializeAsString()); + close(settlementId); + } + return true; +} + +bool SettlementAdapter::processMatchingQuote(const BlockSettle::Terminal::Quote& response) +{ + const auto& itSettl = settlByRfqId_.find(response.request_id()); + if (itSettl == settlByRfqId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, response.request_id()); + return true; + } + itSettl->second->quote = fromMsg(response); + const auto& itQuote = settlByQuoteId_.find(response.quote_id()); + if (itQuote == settlByQuoteId_.end()) { + settlByQuoteId_[response.quote_id()] = itSettl->second; + } + SettlementMessage msg; + *msg.mutable_quote() = response; + return pushResponse(user_, itSettl->second->env.sender, msg.SerializeAsString()); +} + +bool SettlementAdapter::processMatchingOrder(const MatchingMessage_Order& response) +{ + const auto& itSettl = settlByQuoteId_.find(response.quote_id()); + if (itSettl == settlByQuoteId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, response.quote_id()); + return true; + } + + SettlementMessage msg; + const auto& order = fromMsg(response); + if (order.status == bs::network::Order::Status::Filled) { + auto msgResp = msg.mutable_matched_quote(); + msgResp->set_rfq_id(itSettl->second->rfq.requestId); + msgResp->set_quote_id(response.quote_id()); + msgResp->set_price(response.price()); + } + else if (order.status == bs::network::Order::Status::Failed) { + auto msgResp = msg.mutable_failed_settlement(); + msgResp->set_rfq_id(itSettl->second->rfq.requestId); + msgResp->set_quote_id(response.quote_id()); + msgResp->set_info(order.info); + } + else if (order.status == bs::network::Order::Status::Pending) { + if (/*itSettl->second->dealer &&*/ (itSettl->second->quote.assetType == bs::network::Asset::SpotXBT) + && (itSettl->second->quote.quotingType == bs::network::Quote::Tradeable)) { + if (((itSettl->second->quote.side == bs::network::Side::Buy) || + (itSettl->second->quote.product != bs::network::XbtCurrency)) + && itSettl->second->dealerRecvAddress.empty()) { + //maybe obtain receiving address here instead of when constructing pay-out in WalletsAdapter + } + if (startXbtSettlement(itSettl->second->quote)) { + logger_->debug("[{}] started XBT settlement on {}", __func__, response.quote_id()); + } else { + return true; + } + } + auto msgResp = msg.mutable_pending_settlement(); + auto msgIds = msgResp->mutable_ids(); + msgIds->set_rfq_id(itSettl->second->rfq.requestId); + msgIds->set_quote_id(response.quote_id()); + msgIds->set_settlement_id(response.settlement_id()); + msgResp->set_time_left_ms(kHandshakeTimeout.count() * 1000); + } + else { + logger_->debug("[{}] {} unprocessed order status {}", __func__, order.quoteId + , (int)order.status); + return true; + } + return pushResponse(user_, itSettl->second->env.sender, msg.SerializeAsString()); +} + +bool SettlementAdapter::processMatchingInRFQ(const IncomingRFQ& qrn) +{ + SettlementMessage msgBC; + *msgBC.mutable_quote_req_notif() = qrn; + + const auto& rfq = fromMsg(qrn.rfq()); + const auto& settlement = std::make_shared(); + settlement->rfq = rfq; + settlement->dealer = true; + try { + settlement->settlementId = BinaryData::CreateFromHex(qrn.settlement_id()); + } + catch (const std::exception&) { + logger_->error("[{}] invalid settlement id", __func__); + } + settlByRfqId_[rfq.requestId] = settlement; + + SettlementMessage msg; + msg.set_quote_req_timeout(rfq.requestId); + const auto& timeNow = bs::message::bus_clock::now(); + const auto expTime = std::chrono::milliseconds(qrn.expiration_ms()) - timeNow.time_since_epoch(); + if (expTime.count() < 0) { + logger_->error("[{}] outdated expiry {} for {}", __func__, expTime.count(), rfq.requestId); + return true; + } + return (pushRequest(user_, user_, msg.SerializeAsString(), timeNow + expTime) + && pushBroadcast(user_, msgBC.SerializeAsString())); +} + +bool SettlementAdapter::processBsUnsignedPayin(const BinaryData& settlementId) +{ + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl == settlBySettlId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, settlementId.toHexStr()); + return true; + } + if (itSettl->second->ownAuthAddr.empty() || itSettl->second->counterKey.empty()) { + return false; // postpone processing until auth addresses are set + } + + logger_->debug("[{}] {} {}", __func__, settlementId.toHexStr(), itSettl->second->amount.GetValue()); + WalletsMessage msg; + auto msgReq = msg.mutable_payin_request(); + msgReq->set_own_auth_address(itSettl->second->ownAuthAddr.display()); + msgReq->set_counter_auth_address(itSettl->second->counterAuthAddr.display()); + msgReq->set_counter_auth_pubkey(itSettl->second->counterKey.toBinStr()); + msgReq->set_settlement_id(settlementId.toBinStr()); + msgReq->set_reserve_id(itSettl->second->reserveId); + msgReq->set_amount(itSettl->second->amount.GetValue()); + const auto msgId = pushRequest(user_, userWallets_, msg.SerializeAsString()); + if (msgId) { + payinRequests_[msgId] = settlementId; + } + return true; +} + +bs::sync::PasswordDialogData SettlementAdapter::getDialogData(const QDateTime& timestamp + , const Settlement& settlement) const +{ + bs::sync::PasswordDialogData dialogData; + dialogData.setValue(bs::sync::PasswordDialogData::SettlementId, settlement.settlementId.toHexStr()); + dialogData.setValue(bs::sync::PasswordDialogData::DurationLeft + , (int)((kHandshakeTimeout.count() - 1) * 1000)); + dialogData.setValue(bs::sync::PasswordDialogData::DurationTotal + , (int)(kHandshakeTimeout.count() * 1000)); + + // Set timestamp that will be used by auth eid server to update timers. + dialogData.setValue(bs::sync::PasswordDialogData::DurationTimestamp, static_cast(timestamp.toSecsSinceEpoch())); + + dialogData.setValue(bs::sync::PasswordDialogData::ProductGroup + , QObject::tr(bs::network::Asset::toString(settlement.rfq.assetType))); + dialogData.setValue(bs::sync::PasswordDialogData::Security, settlement.rfq.security); + dialogData.setValue(bs::sync::PasswordDialogData::Product, settlement.rfq.product); + dialogData.setValue(bs::sync::PasswordDialogData::Side + , QObject::tr(bs::network::Side::toString(settlement.rfq.side))); + dialogData.setValue(bs::sync::PasswordDialogData::ExpandTxInfo, true); //TODO: make configurable? + + dialogData.setValue(bs::sync::PasswordDialogData::Market, "XBT"); + dialogData.setValue(bs::sync::PasswordDialogData::AutoSignCategory + , static_cast(settlement.dealer ? bs::signer::AutoSignCategory::SettlementDealer + : bs::signer::AutoSignCategory::SettlementRequestor)); + + dialogData.setValue(bs::sync::PasswordDialogData::SettlementId, settlement.settlementId.toHexStr()); + dialogData.setValue(bs::sync::PasswordDialogData::SettlementAddress, settlement.settlementAddress.display()); + + // rfq details + QString qtyProd = UiUtils::XbtCurrency; + QString fxProd = QString::fromStdString(settlement.fxProduct); + + dialogData.setValue(bs::sync::PasswordDialogData::Price, UiUtils::displayPriceXBT(settlement.quote.price)); + dialogData.setValue(bs::sync::PasswordDialogData::FxProduct, fxProd); + + bool isFxProd = (settlement.rfq.product != bs::network::XbtCurrency); + + if (isFxProd) { + dialogData.setValue(bs::sync::PasswordDialogData::Quantity, QObject::tr("%1 %2") + .arg(UiUtils::displayAmountForProduct(settlement.rfq.quantity, fxProd, bs::network::Asset::Type::SpotXBT)) + .arg(fxProd)); + + dialogData.setValue(bs::sync::PasswordDialogData::TotalValue, QObject::tr("%1 XBT") + .arg(UiUtils::displayAmount(settlement.rfq.quantity / settlement.quote.price))); + } else { + dialogData.setValue(bs::sync::PasswordDialogData::Quantity, QObject::tr("%1 XBT") + .arg(UiUtils::displayAmount(settlement.amount))); + + dialogData.setValue(bs::sync::PasswordDialogData::TotalValue, QObject::tr("%1 %2") + .arg(UiUtils::displayAmountForProduct(settlement.amount.GetValueBitcoin() * settlement.quote.price + , fxProd, bs::network::Asset::Type::SpotXBT)) + .arg(fxProd)); + } + + dialogData.setValue(bs::sync::PasswordDialogData::RequesterAuthAddress, settlement.ownAuthAddr.display()); + dialogData.setValue(bs::sync::PasswordDialogData::RequesterAuthAddressVerified, true); + dialogData.setValue(bs::sync::PasswordDialogData::TxInputProduct, UiUtils::XbtCurrency); + + dialogData.setValue(bs::sync::PasswordDialogData::ResponderAuthAddress, settlement.counterAuthAddr.display()); + dialogData.setValue(bs::sync::PasswordDialogData::IsDealer, settlement.dealer); + + return dialogData; +} + +bs::sync::PasswordDialogData SettlementAdapter::getPayinDialogData(const QDateTime& timestamp + , const Settlement& settlement) const +{ + auto dlgData = getDialogData(timestamp, settlement); + dlgData.setValue(bs::sync::PasswordDialogData::ResponderAuthAddressVerified, true); //TODO: put actual value + dlgData.setValue(bs::sync::PasswordDialogData::SigningAllowed, true); //TODO: same value here + + dlgData.setValue(bs::sync::PasswordDialogData::Title, QObject::tr("Settlement Pay-In")); + dlgData.setValue(bs::sync::PasswordDialogData::SettlementPayInVisible, true); + return dlgData; +} + +bs::sync::PasswordDialogData SettlementAdapter::getPayoutDialogData(const QDateTime& timestamp + , const Settlement& settlement) const +{ + auto dlgData = getDialogData(timestamp, settlement); + dlgData.setValue(bs::sync::PasswordDialogData::Title, QObject::tr("Settlement Pay-Out")); + dlgData.setValue(bs::sync::PasswordDialogData::SettlementPayOutVisible, true); + + dlgData.setValue(bs::sync::PasswordDialogData::ResponderAuthAddressVerified, true); + dlgData.setValue(bs::sync::PasswordDialogData::SigningAllowed, true); + return dlgData; +} + +bool SettlementAdapter::processBsSignPayin(const BsServerMessage_SignXbtHalf& request) +{ + const auto &settlementId = BinaryData::fromString(request.settlement_id()); + const auto& payinHash = BinaryData::fromString(request.payin_hash()); + logger_->debug("[{}] {}: payin hash {}", __func__, settlementId.toHexStr() + , payinHash.toHexStr()); + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl == settlBySettlId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, settlementId.toHexStr()); + return true; + } + if (!itSettl->second->payin.isValid()) { + logger_->error("[{}] invalid payin TX for {}", __func__, settlementId.toHexStr()); + unreserve(itSettl->second->rfq.requestId); + SettlementMessage msg; + auto msgFail = msg.mutable_failed_settlement(); + msgFail->set_settlement_id(settlementId.toBinStr()); + msgFail->set_info("invalid handshake flow"); + pushResponse(user_, itSettl->second->env.sender, msg.SerializeAsString()); + settlByQuoteId_.erase(itSettl->second->quote.quoteId); + settlBySettlId_.erase(itSettl); + return true; + } + pendingZCs_[payinHash] = settlementId; + itSettl->second->payin.txHash = payinHash; + auto dlgData = getPayinDialogData(QDateTime::fromMSecsSinceEpoch(request.timestamp()) + , *itSettl->second); + SignerMessage msg; + auto msgReq = msg.mutable_sign_settlement_tx(); + msgReq->set_settlement_id(settlementId.toBinStr()); + *msgReq->mutable_tx_request() = bs::signer::coreTxRequestToPb(itSettl->second->payin); + *msgReq->mutable_details() = dlgData.toProtobufMessage(); + const auto msgId = pushRequest(user_, userSigner_, msg.SerializeAsString()); + if (msgId) { + payinRequests_[msgId] = settlementId; + return true; + } + return false; +} + +bool SettlementAdapter::processBsSignPayout(const BsServerMessage_SignXbtHalf& request) +{ + const auto settlementId = BinaryData::fromString(request.settlement_id()); + logger_->debug("[{}] {}", __func__, settlementId.toHexStr()); + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl == settlBySettlId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, settlementId.toHexStr()); + return true; + } + const bs::Address& recvAddr = itSettl->second->dealer ? + itSettl->second->dealerRecvAddress : itSettl->second->recvAddress; +/* if (recvAddr.empty()) { + logger_->debug("[{}] waiting for own receiving address", __func__); + return false; + }*/ // recvAddr is now obtained in WalletsAdapter if empty + WalletsMessage msg; + auto msgReq = msg.mutable_payout_request(); + msgReq->set_own_auth_address(itSettl->second->ownAuthAddr.display()); + msgReq->set_settlement_id(settlementId.toBinStr()); + msgReq->set_counter_auth_pubkey(itSettl->second->counterKey.toBinStr()); + msgReq->set_amount(itSettl->second->amount.GetValue()); + msgReq->set_payin_hash(request.payin_hash()); + msgReq->set_recv_address(recvAddr.display()); + const auto msgId = pushRequest(user_, userWallets_, msg.SerializeAsString()); + if (msgId) { + payoutRequests_[msgId] = settlementId; + } + return true; +} + +bool SettlementAdapter::processCancelRFQ(const std::string& rfqId) +{ + const auto& itSettl = settlByRfqId_.find(rfqId); + if (itSettl == settlByRfqId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, rfqId); + return true; + } + settlByRfqId_.erase(itSettl); + unreserve(rfqId); + MatchingMessage msg; + msg.set_cancel_rfq(rfqId); + return pushRequest(user_, userMtch_, msg.SerializeAsString()); +} + +bool SettlementAdapter::processAcceptRFQ(const bs::message::Envelope& env + , const AcceptRFQ& request) +{ + const auto& itSettl = settlByRfqId_.find(request.rfq_id()); + if (itSettl == settlByRfqId_.end()) { + logger_->error("[{}] unknown settlement for {}", __func__, request.rfq_id()); + return true; + } + itSettl->second->env = env; + const auto& quote = fromMsg(request.quote()); + itSettl->second->quote = quote; + settlByQuoteId_[request.quote().quote_id()] = itSettl->second; + settlByRfqId_.erase(itSettl); + + if (quote.assetType == bs::network::Asset::SpotFX) { + MatchingMessage msg; + auto msgReq = msg.mutable_accept_rfq(); + *msgReq = request; + return pushRequest(user_, userMtch_, msg.SerializeAsString()); + } + switch (quote.assetType) { + case bs::network::Asset::SpotXBT: + startXbtSettlement(quote); + break; + case bs::network::Asset::PrivateMarket: + startCCSettlement(quote); + break; + default: + logger_->error("[{}] unknown asset type {}", __func__, (int)quote.assetType); + break; + } +} + +bool SettlementAdapter::processSendRFQ(const bs::message::Envelope& env + , const SettlementMessage_SendRFQ& request) +{ + const auto& rfq = fromMsg(request.rfq()); + const auto &settlement = std::make_shared(Settlement{ env + , false, rfq, request.reserve_id() }); + if ((rfq.assetType != bs::network::Asset::SpotFX) && + (rfq.side == bs::network::Side::Buy)) { + settlement->recvAddress = bs::Address::fromAddressString(rfq.receiptAddress); + } + settlByRfqId_[rfq.requestId] = settlement; + + MatchingMessage msg; + auto msgReq = msg.mutable_send_rfq(); + *msgReq = request.rfq(); + return pushRequest(user_, userMtch_, msg.SerializeAsString()); +} + +bool SettlementAdapter::processSubmitQuote(const bs::message::Envelope& env + , const ReplyToRFQ& request) +{ + const auto& itSettl = settlByRfqId_.find(request.quote().request_id()); + if (itSettl == settlByRfqId_.end()) { + logger_->error("[{}] RFQ {} not found", __func__, request.quote().request_id()); + return true; + } + logger_->debug("[{}] sess token: {}, account: {}, recv addr: {}", __func__ + , request.session_token(), request.account(), request.dealer_recv_addr()); + itSettl->second->quote = fromMsg(request.quote()); + itSettl->second->env = env; + if (!request.dealer_recv_addr().empty()) { + try { + itSettl->second->dealerRecvAddress = bs::Address::fromAddressString(request.dealer_recv_addr()); + } catch (const std::exception&) { + logger_->warn("[{}] invalid dealer recv address {}", __func__, request.dealer_recv_addr()); + } + } + + BinaryData settlementId; + try { + settlementId = BinaryData::CreateFromHex(itSettl->second->quote.settlementId); + } + catch (const std::exception&) { + logger_->warn("[{}] invalid settlementId format: {}", __func__, itSettl->second->quote.settlementId); + } + if (!settlementId.empty()) { + if (!itSettl->second->settlementId.empty() && (itSettl->second->settlementId != settlementId)) { + logger_->error("[{}] settlementId mismatch", __func__); + return true; + } + itSettl->second->settlementId = settlementId; + itSettl->second->reserveId = itSettl->second->quote.requestId; + settlBySettlId_[settlementId] = itSettl->second; + } + + MatchingMessage msg; + *msg.mutable_submit_quote_notif() = request; + return pushRequest(user_, userMtch_, msg.SerializeAsString()); +} + +bool SettlementAdapter::processPullQuote(const bs::message::Envelope& env + , const PullRFQReply& request) +{ + const auto& itSettl = settlByRfqId_.find(request.rfq_id()); + if (itSettl == settlByRfqId_.end()) { + logger_->error("[{}] RFQ {} not found", __func__, request.rfq_id()); + return true; + } + if (!itSettl->second->env.sender || (itSettl->second->env.sender->value() != env.sender->value())) { + logger_->error("[{}] invalid or different sender of quote submit", __func__); + return true; + } + + const auto& itSettlById = settlBySettlId_.find(itSettl->second->settlementId); + if (itSettlById != settlBySettlId_.end()) { + unreserve(itSettl->first); + settlBySettlId_.erase(itSettlById); + } + + MatchingMessage msg; + auto msgData = msg.mutable_pull_quote_notif(); + *msgData = request; + return pushRequest(user_, userMtch_, msg.SerializeAsString()); +} + +bool SettlementAdapter::processQuoteCancelled(const QuoteCancelled& request) +{ + const auto& itRFQ = settlByRfqId_.find(request.rfq_id()); + if (itRFQ == settlByRfqId_.end()) { + logger_->error("[{}] unknown RFQ {}", __func__, request.rfq_id()); + return true; + } + const auto& itQuote = settlByQuoteId_.find(request.quote_id()); + if (itQuote == settlByQuoteId_.end()) { + logger_->error("[{}] quote {} not found", __func__, request.quote_id()); + return true; + } + settlByQuoteId_.erase(itQuote); + + SettlementMessage msg; + *msg.mutable_quote_cancelled() = request; + return pushResponse(user_, itRFQ->second->env.sender, msg.SerializeAsString()); +} + +bool SettlementAdapter::processXbtTx(uint64_t msgId, const WalletsMessage_XbtTxResponse& response) +{ + const auto& itPayin = payinRequests_.find(msgId); + if (itPayin != payinRequests_.end()) { + const auto& itSettl = settlBySettlId_.find(itPayin->second); + if (itSettl != settlBySettlId_.end()) { + if (response.error_text().empty()) { + itSettl->second->payin = bs::signer::pbTxRequestToCore(response.tx_request(), logger_); + try { + itSettl->second->settlementAddress = bs::Address::fromAddressString(response.settlement_address()); + } + catch (const std::exception&) { + logger_->error("[{}] invalid settlement address", __func__); + } + BsServerMessage msg; + auto msgReq = msg.mutable_send_unsigned_payin(); + msgReq->set_settlement_id(itPayin->second.toBinStr()); + msgReq->set_tx(itSettl->second->payin.serializeState().SerializeAsString()); + pushRequest(user_, userBS_, msg.SerializeAsString()); + } + else { + SettlementMessage msg; + auto msgResp = msg.mutable_failed_settlement(); + msgResp->set_rfq_id(itSettl->second->rfq.requestId); + msgResp->set_settlement_id(itPayin->second.toBinStr()); + msgResp->set_info(response.error_text()); + pushResponse(user_, itSettl->second->env.sender, msg.SerializeAsString()); + cancel(itPayin->second); + } + } + else { + logger_->error("[{}] settlement {} for payin not found", __func__ + , itPayin->second.toHexStr()); + } + payinRequests_.erase(itPayin); + return true; + } + const auto& itPayout = payoutRequests_.find(msgId); + if (itPayout != payoutRequests_.end()) { + const auto& itSettl = settlBySettlId_.find(itPayout->second); + if (itSettl != settlBySettlId_.end()) { + logger_->debug("[{}] got payout {}", __func__, itPayout->second.toHexStr()); + try { + itSettl->second->settlementAddress = bs::Address::fromAddressString(response.settlement_address()); + } catch (const std::exception&) { + logger_->error("[{}] invalid settlement address", __func__); + } + auto dlgData = getPayoutDialogData(QDateTime::currentDateTime(), *itSettl->second); + SignerMessage msg; + auto msgReq = msg.mutable_sign_settlement_tx(); + msgReq->set_settlement_id(itPayout->second.toBinStr()); + *msgReq->mutable_tx_request() = response.tx_request(); + *msgReq->mutable_details() = dlgData.toProtobufMessage(); + msgReq->set_contra_auth_pubkey(itSettl->second->counterKey.toBinStr()); + msgReq->set_own_key_first(true); + const auto msgId = pushRequest(user_, userSigner_, msg.SerializeAsString()); + if (msgId) { + payoutRequests_[msgId] = itPayout->second; + } + } + else { + logger_->error("[{}] settlement {} for payout not found", __func__ + , itPayout->second.toHexStr()); + } + payoutRequests_.erase(itPayout); + return true; + } + logger_->error("[{}] unknown XBT TX response #{}", __func__, msgId); + return true; +} + +bool SettlementAdapter::processSignedTx(uint64_t msgId + , const SignerMessage_SignTxResponse& response) +{ + const auto& settlementId = BinaryData::fromString(response.id()); + logger_->debug("[{}] {}", __func__, settlementId.toHexStr()); + const auto& sendSignedTx = [this, response, settlementId](bool payin) + { + if (static_cast(response.error_code()) == bs::error::ErrorCode::TxCancelled) { + cancel(settlementId); + return; + } + BsServerMessage msg; + auto msgReq = payin ? msg.mutable_send_signed_payin() : msg.mutable_send_signed_payout(); + msgReq->set_settlement_id(settlementId.toBinStr()); + msgReq->set_tx(response.signed_tx()); + logger_->debug("[SettlementAdapter::processSignedTx::sendSignedTX] {}" + , BinaryData::fromString(response.signed_tx()).toHexStr()); + pushRequest(user_, userBS_, msg.SerializeAsString()); + }; + const auto& itPayin = payinRequests_.find(msgId); + if (itPayin != payinRequests_.end()) { + if (itPayin->second != settlementId) { + logger_->error("[{}] payin settlement id {} mismatch", __func__ + , settlementId.toHexStr()); + payinRequests_.erase(itPayin); + return true; //TODO: decide the consequences of this + } + const auto& itSettl = settlBySettlId_.find(itPayin->second); + payinRequests_.erase(itPayin); + if (itSettl == settlBySettlId_.end()) { + logger_->error("[{}] settlement for {} not found", __func__ + , settlementId.toHexStr()); + return true; + } + itSettl->second->handshakeComplete = true; + sendSignedTx(true); + return true; + } + + const auto& itPayout = payoutRequests_.find(msgId); + if (itPayout != payoutRequests_.end()) { + if (itPayout->second != settlementId) { + logger_->error("[{}] payout settlement id mismatch", __func__); + payoutRequests_.erase(itPayout); + return true; //TODO: decide the consequences of this + } + try { + const Tx tx(BinaryData::fromString(response.signed_tx())); + pendingZCs_[tx.getThisHash()] = itPayout->second; + } + catch (const std::exception& e) { + logger_->error("[{}] invalid signed payout TX: {}", __func__, e.what()); + cancel(itPayout->second); + payoutRequests_.erase(itPayout); + return true; + } + const auto& itSettl = settlBySettlId_.find(itPayout->second); + payoutRequests_.erase(itPayout); + if (itSettl == settlBySettlId_.end()) { + logger_->error("[{}] settlement for {} not found", __func__ + , settlementId.toHexStr()); + return true; + } + itSettl->second->handshakeComplete = true; + sendSignedTx(false); + return true; + } + logger_->error("[{}] unknown signed TX #{}", __func__, msgId); + return true; +} + +bool SettlementAdapter::processHandshakeTimeout(const std::string& id) +{ + const auto& settlementId = BinaryData::fromString(id); + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl != settlBySettlId_.end()) { + if (!itSettl->second->handshakeComplete) { + logger_->error("[{}] settlement {} handshake timeout", __func__ + , settlementId.toHexStr()); + SettlementMessage msg; + auto msgFail = msg.mutable_failed_settlement(); + msgFail->set_settlement_id(settlementId.toBinStr()); + msgFail->set_info("handshake timeout"); + return pushResponse(user_, itSettl->second->env.sender, msg.SerializeAsString()); + } + } + return true; +} + +bool SettlementAdapter::processInRFQTimeout(const std::string& id) +{ + const auto& itSettl = settlByRfqId_.find(id); + if (itSettl != settlByRfqId_.end()) { // do nothing - just remove unanswered RFQ + logger_->debug("[{}] {}", __func__, id); + settlByRfqId_.erase(itSettl); + } + return true; +} + +bool SettlementAdapter::startXbtSettlement(const bs::network::Quote& quote) +{ + const auto& sendFailedQuote = [this, quote](const std::string& info) + { + logger_->error("[SettlementAdapter::startXbtSettlement] {} - aborting " + "settlement", info); + unreserve(quote.requestId); + SettlementMessage msg; + auto msgResp = msg.mutable_failed_settlement(); + msgResp->set_rfq_id(quote.requestId); + msgResp->set_quote_id(quote.quoteId); + msgResp->set_info(info); + pushBroadcast(user_, msg.SerializeAsString()); + }; + const auto& itSettl = settlByQuoteId_.find(quote.quoteId); + if (itSettl == settlByQuoteId_.end()) { + sendFailedQuote("unknown quote id"); + return false; + } + if (quote.settlementId.empty()) { + sendFailedQuote("no settlement id"); + settlByQuoteId_.erase(itSettl); + return false; + } + + CurrencyPair cp(quote.security); + const bool isFxProd = (quote.product != bs::network::XbtCurrency); + itSettl->second->fxProduct = cp.ContraCurrency(bs::network::XbtCurrency); + const double amount = isFxProd ? quote.quantity / quote.price : quote.quantity; + const auto xbtAmount = bs::XBTAmount(amount); + itSettl->second->amount = xbtAmount; + + // BST-2545: Use price as it see Genoa (and it computes it as ROUNDED_CCY / XBT) + const auto actualXbtPrice = UiUtils::actualXbtPrice(xbtAmount, quote.price); + itSettl->second->actualXbtPrice = actualXbtPrice; + + const auto side = (quote.product == bs::network::XbtCurrency) + ? bs::network::Side::invert(quote.side) : quote.side; + itSettl->second->txComment = fmt::format("{} {} @ {}", bs::network::Side::toString(side) + , quote.security, UiUtils::displayPriceXBT(actualXbtPrice).toStdString()); +// itSettl->second.dealerAddrValidationReqd = xbtAmount > bs::XBTAmount(tier1XbtLimit); + + BinaryData settlementId; + try { + settlementId = BinaryData::CreateFromHex(quote.settlementId); + } + catch (const std::exception&) { + sendFailedQuote("invalid settlement id format"); + settlByQuoteId_.erase(itSettl); + return false; + } + itSettl->second->settlementId = settlementId; + + auto &data = settlBySettlId_[settlementId] = itSettl->second; + try { + if (data->dealer) { + if (data->ownKey.empty()) { + data->ownKey = BinaryData::CreateFromHex(quote.dealerAuthPublicKey); + } + if (data->counterKey.empty()) { + if (quote.requestorAuthPublicKey.empty()) { + data->counterKey = BinaryData::CreateFromHex(data->rfq.requestorAuthPublicKey); + } + else { + data->counterKey = BinaryData::CreateFromHex(quote.requestorAuthPublicKey); + } + } + } else { + data->ownKey = BinaryData::CreateFromHex(quote.requestorAuthPublicKey); + data->counterKey = BinaryData::CreateFromHex(quote.dealerAuthPublicKey); + } + data->ownAuthAddr = bs::Address::fromPubKey(data->ownKey, AddressEntryType_P2WPKH); + data->counterAuthAddr = bs::Address::fromPubKey(data->counterKey, AddressEntryType_P2WPKH); + } + catch (const std::exception&) { + sendFailedQuote("failed to decode data"); + settlBySettlId_.erase(data->settlementId); + settlByQuoteId_.erase(itSettl); + return false; + } + + const auto& timeNow = bs::message::bus_clock::now(); + SettlementMessage msg; + msg.set_handshake_timeout(settlementId.toBinStr()); + pushRequest(user_, user_, msg.SerializeAsString(), timeNow + kHandshakeTimeout); + + if (data->dealerAddrValidationReqd) { + //TODO: push counterAuthAddr to OnChainTracker for checking + } + + if (!itSettl->second->dealer) { + MatchingMessage msgMtch; + auto msgReq = msgMtch.mutable_accept_rfq(); + msgReq->set_rfq_id(quote.requestId); + toMsg(quote, msgReq->mutable_quote()); + msgReq->set_payout_tx("not used"); // copied from ReqXBTSettlementContainer + return pushRequest(user_, userMtch_, msgMtch.SerializeAsString()); + } + return true; +} + +void SettlementAdapter::startCCSettlement(const bs::network::Quote&) +{ +} + +void SettlementAdapter::unreserve(const std::string& id, const std::string &subId) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_unreserve_utxos(); + msgReq->set_id(id); + msgReq->set_sub_id(subId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void SettlementAdapter::close(const BinaryData& settlementId) +{ + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl == settlBySettlId_.end()) { + return; + } + logger_->debug("[{}] {}", __func__, settlementId.toHexStr()); + unreserve(itSettl->second->rfq.requestId); + settlByQuoteId_.erase(itSettl->second->quote.quoteId); + settlBySettlId_.erase(itSettl); +} + +void SettlementAdapter::cancel(const BinaryData& settlementId) +{ + const auto& itSettl = settlBySettlId_.find(settlementId); + if (itSettl == settlBySettlId_.end()) { + return; + } + const auto sender = itSettl->second->env.sender; + close(settlementId); + SettlementMessage msg; + msg.set_settlement_cancelled(settlementId.toBinStr()); + pushResponse(user_, sender, msg.SerializeAsString()); +} diff --git a/Core/unused/SettlementAdapter.h b/Core/unused/SettlementAdapter.h new file mode 100644 index 000000000..faa36c413 --- /dev/null +++ b/Core/unused/SettlementAdapter.h @@ -0,0 +1,133 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef SETTLEMENT_ADAPTER_H +#define SETTLEMENT_ADAPTER_H + +#include "CommonTypes.h" +#include "CoreWallet.h" +#include "Message/Adapter.h" +#include "PasswordDialogData.h" + +namespace spdlog { + class logger; +} +namespace BlockSettle { + namespace Common { + class ArmoryMessage_ZCReceived; + class SignerMessage_SignTxResponse; + class WalletsMessage_XbtTxResponse; + } + namespace Terminal { + class AcceptRFQ; + class BsServerMessage_SignXbtHalf; + class IncomingRFQ; + class MatchingMessage_Order; + class PullRFQReply; + class Quote; + class QuoteCancelled; + class ReplyToRFQ; + class SettlementMessage_SendRFQ; + } +} + +class SettlementAdapter : public bs::message::Adapter +{ +public: + SettlementAdapter(const std::shared_ptr &); + ~SettlementAdapter() override = default; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + std::set> supportedReceivers() const override { + return { user_ }; + } + std::string name() const override { return "Settlement"; } + +private: + bool processZC(const BlockSettle::Common::ArmoryMessage_ZCReceived&); + bool processMatchingQuote(const BlockSettle::Terminal::Quote&); + bool processMatchingOrder(const BlockSettle::Terminal::MatchingMessage_Order&); + bool processMatchingInRFQ(const BlockSettle::Terminal::IncomingRFQ&); + + bool processBsUnsignedPayin(const BinaryData& settlementId); + bool processBsSignPayin(const BlockSettle::Terminal::BsServerMessage_SignXbtHalf&); + bool processBsSignPayout(const BlockSettle::Terminal::BsServerMessage_SignXbtHalf&); + + bool processCancelRFQ(const std::string& rfqId); + bool processAcceptRFQ(const bs::message::Envelope& + , const BlockSettle::Terminal::AcceptRFQ&); + bool processSendRFQ(const bs::message::Envelope& + , const BlockSettle::Terminal::SettlementMessage_SendRFQ&); + + bool processSubmitQuote(const bs::message::Envelope&, const BlockSettle::Terminal::ReplyToRFQ&); + bool processPullQuote(const bs::message::Envelope&, const BlockSettle::Terminal::PullRFQReply&); + bool processQuoteCancelled(const BlockSettle::Terminal::QuoteCancelled&); + + bool processXbtTx(uint64_t msgId, const BlockSettle::Common::WalletsMessage_XbtTxResponse&); + bool processSignedTx(uint64_t msgId, const BlockSettle::Common::SignerMessage_SignTxResponse&); + + bool processHandshakeTimeout(const std::string& id); + bool processInRFQTimeout(const std::string& id); + + bool startXbtSettlement(const bs::network::Quote&); + void startCCSettlement(const bs::network::Quote&); + void unreserve(const std::string& id, const std::string& subId = {}); + void cancel(const BinaryData& settlementId); + void close(const BinaryData& settlementId); + +private: + std::shared_ptr logger_; + std::shared_ptr user_, userMtch_, userWallets_, userBS_; + std::shared_ptr userSigner_; + + struct Settlement { + bs::message::Envelope env; + bool dealer{ false }; + bs::network::RFQ rfq; + std::string reserveId; + bs::network::Quote quote; + std::string fxProduct; + bs::XBTAmount amount; + double actualXbtPrice; + bool dealerAddrValidationReqd{ false }; + BinaryData settlementId; + bs::Address settlementAddress; + std::string txComment; + bs::Address recvAddress; + bs::Address dealerRecvAddress; + bs::Address ownAuthAddr; + BinaryData ownKey; + BinaryData counterKey; + bs::Address counterAuthAddr; + bs::core::wallet::TXSignRequest payin; + bool otc{ false }; + bool handshakeComplete{ false }; + }; + std::unordered_map> settlByRfqId_; + std::unordered_map> settlByQuoteId_; + std::map> settlBySettlId_; + std::map pendingZCs_; + + std::map payinRequests_; + std::map payoutRequests_; + +private: + bs::sync::PasswordDialogData getDialogData(const QDateTime& timestamp + , const Settlement &) const; + bs::sync::PasswordDialogData getPayinDialogData(const QDateTime& timestamp + , const Settlement&) const; + bs::sync::PasswordDialogData getPayoutDialogData(const QDateTime& timestamp + , const Settlement&) const; +}; + + +#endif // SETTLEMENT_ADAPTER_H diff --git a/GUI/CMakeLists.txt b/GUI/CMakeLists.txt new file mode 100644 index 000000000..249175835 --- /dev/null +++ b/GUI/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# +# *********************************************************************************** +# * Copyright (C) 2020 - 2022, BlockSettle AB +# * Distributed under the GNU Affero General Public License (AGPL v3) +# * See LICENSE or http://www.gnu.org/licenses/agpl.html +# * +# ********************************************************************************** +# +# + +ADD_SUBDIRECTORY(QtWidgets) +ADD_SUBDIRECTORY(QtQuick) diff --git a/GUI/QtQuick/CMakeLists.txt b/GUI/QtQuick/CMakeLists.txt new file mode 100644 index 000000000..2aa835366 --- /dev/null +++ b/GUI/QtQuick/CMakeLists.txt @@ -0,0 +1,75 @@ +# +# +# *********************************************************************************** +# * Copyright (C) 2020 - 2021, BlockSettle AB +# * Distributed under the GNU Affero General Public License (AGPL v3) +# * See LICENSE or http://www.gnu.org/licenses/agpl.html +# * +# ********************************************************************************** +# +# + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +PROJECT(${TERMINAL_GUI_QTQUICK_NAME}) + +FILE(GLOB SOURCES + *.cpp +) +FILE(GLOB HEADERS + *.h +) + +INCLUDE_DIRECTORIES(${COMMON_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${BOTAN_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${WALLET_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${COMMON_UI_LIB_INCLUDE_DIR} ) +INCLUDE_DIRECTORIES(${BS_COMMON_ENUMS_INCLUDE_DIR} ) +INCLUDE_DIRECTORIES(${BS_COMMUNICATION_INCLUDE_DIR} ) +INCLUDE_DIRECTORIES(${BLOCKSETTLE_UI_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${Qt5Svg_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${Qt5Gui_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Core_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${Qt5Network_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Qml_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5DBus_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Charts_INCLUDE_DIRS} ) + +qt5_add_resources(GENERATED_RESOURCES qtquick.qrc) + +ADD_LIBRARY(${TERMINAL_GUI_QTQUICK_NAME} + ${SOURCES} + ${HEADERS} + ${GENERATED_RESOURCES} +) + +TARGET_INCLUDE_DIRECTORIES(${TERMINAL_GUI_QTQUICK_NAME} + PUBLIC ${BLOCK_SETTLE_ROOT}/GUI/QtQuick + PRIVATE ${BLOCK_SETTLE_ROOT}/Core + PRIVATE ${BLOCK_SETTLE_ROOT}/common/BlocksettleNetworkingLib + PRIVATE ${BLOCK_SETTLE_ROOT}/BlockSettleUILib +) + +TARGET_LINK_LIBRARIES(${TERMINAL_GUI_QTQUICK_NAME} + ${BLOCKSETTLE_UI_LIBRARY_NAME} + ${BS_NETWORK_LIB_NAME} + ${CRYPTO_LIB_NAME} + ${QT_LINUX_LIBS} + ${QT_QUICK_LIBS} + Qt5::Network + Qt5::Core + Qt5::Widgets + Qt5::Qml + Qt5::QmlWorkerScript + Qt5::Quick + Qt5::Gui + Qt5::Network + Qt5::PrintSupport + Qt5::Core + Qt5::Svg + Qt5::DBus + ${QT_LIBS} + ${OS_SPECIFIC_LIBS} + ${OPENSSL_LIBS} +) diff --git a/GUI/QtQuick/QtQuickAdapter.cpp b/GUI/QtQuick/QtQuickAdapter.cpp new file mode 100644 index 000000000..b5f3e464d --- /dev/null +++ b/GUI/QtQuick/QtQuickAdapter.cpp @@ -0,0 +1,906 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2022, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "CommonTypes.h" +#include "QtQuickAdapter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "BSMessageBox.h" +#include "BSTerminalSplashScreen.h" +#include "Wallets/ProtobufHeadlessUtils.h" +#include "SettingsAdapter.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + + +namespace { + std::shared_ptr staticLogger; +} +// redirect qDebug() to the log +// stdout redirected to parent process +void qMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + switch (type) { + case QtDebugMsg: + staticLogger->debug("[QML] {}", localMsg.constData()); + break; + case QtInfoMsg: + staticLogger->info("[QML] {}", localMsg.constData()); + break; + case QtWarningMsg: + staticLogger->warn("[QML] {}", localMsg.constData()); + break; + case QtCriticalMsg: + staticLogger->error("[QML] {}", localMsg.constData()); + break; + case QtFatalMsg: + staticLogger->critical("[QML] {}", localMsg.constData()); + break; + } +} + +static void checkStyleSheet(QApplication& app) +{ + QLatin1String styleSheetFileName = QLatin1String("stylesheet.css"); + + QFileInfo info = QFileInfo(QLatin1String(styleSheetFileName)); + + static QDateTime lastTimestamp = info.lastModified(); + + if (lastTimestamp == info.lastModified()) { + return; + } + + lastTimestamp = info.lastModified(); + + QFile stylesheetFile(styleSheetFileName); + + bool result = stylesheetFile.open(QFile::ReadOnly); + assert(result); + + app.setStyleSheet(QString::fromLatin1(stylesheetFile.readAll())); +} + +QtQuickAdapter::QtQuickAdapter(const std::shared_ptr &logger) + : QObject(nullptr), logger_(logger) + , userSettings_(std::make_shared(TerminalUsers::Settings)) + , userWallets_(std::make_shared(TerminalUsers::Wallets)) + , userBlockchain_(std::make_shared(TerminalUsers::Blockchain)) + , userSigner_(std::make_shared(TerminalUsers::Signer)) +{ + staticLogger = logger; +} + +QtQuickAdapter::~QtQuickAdapter() +{} + +void QtQuickAdapter::run(int &argc, char **argv) +{ + logger_->debug("[QtQuickAdapter::run]"); + Q_INIT_RESOURCE(armory); + Q_INIT_RESOURCE(qtquick); + +// QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QApplication app(argc, argv); + + QApplication::setOrganizationDomain(QLatin1String("blocksettle.com")); + QApplication::setWindowIcon(QIcon(QStringLiteral(":/images/bs_logo.png"))); + + const QFileInfo localStyleSheetFile(QLatin1String("stylesheet.css")); + QFile stylesheetFile(localStyleSheetFile.exists() + ? localStyleSheetFile.fileName() : QLatin1String(":/STYLESHEET")); + + if (stylesheetFile.open(QFile::ReadOnly)) { + app.setStyleSheet(QString::fromLatin1(stylesheetFile.readAll())); + QPalette p = QApplication::palette(); + p.setColor(QPalette::Disabled, QPalette::Light, QColor(10, 22, 25)); + QApplication::setPalette(p); + } + + // Start monitoring to update stylesheet live when file is changed on the disk + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, &app, [&app] { + checkStyleSheet(app); + }); + timer.start(100); + + const QString location = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + const QString lockFilePath = location + QLatin1String("/blocksettle.lock"); + QLockFile lockFile(lockFilePath); + lockFile.setStaleLockTime(0); + + if (!lockFile.tryLock()) { + BSMessageBox box(BSMessageBox::info, app.tr("BlockSettle Terminal") + , app.tr("BlockSettle Terminal is already running") + , app.tr("Stop the other BlockSettle Terminal instance. If no other " \ + "instance is running, delete the lockfile (%1).").arg(lockFilePath)); + box.exec(); + return; + } + + qInstallMessageHandler(qMessageHandler); + + QString logoIcon; + logoIcon = QLatin1String(":/FULL_BS_LOGO"); + + QPixmap splashLogo(logoIcon); + const int splashScreenWidth = 400; + { + std::lock_guard lock(mutex_); + splashScreen_ = new BSTerminalSplashScreen(splashLogo.scaledToWidth(splashScreenWidth + , Qt::SmoothTransformation)); + } + updateSplashProgress(); + splashScreen_->show(); + QMetaObject::invokeMethod(splashScreen_, [this] { + QTimer::singleShot(5000, [this] { + splashProgressCompleted(); + }); + }); + + logger_->debug("[QtGuiAdapter::run] creating QML app"); + QQmlApplicationEngine engine; + QQuickWindow::setTextRenderType(QQuickWindow::NativeTextRendering); + const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); + engine.rootContext()->setContextProperty(QStringLiteral("fixedFont"), fixedFont); + engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + if (engine.rootObjects().empty()) { + BSMessageBox box(BSMessageBox::critical, app.tr("BlockSettle Terminal") + , app.tr("Failed to load QML GUI"), app.tr("See log for details")); + box.exec(); + return; + } + rootObj_ = engine.rootObjects().at(0); + if (loadingDone_) { + auto window = qobject_cast(rootObj_); + if (window) { + window->show(); + } + } + + updateStates(); + + requestInitialSettings(); + logger_->debug("[QtGuiAdapter::run] initial setup done"); + app.exec(); +} + +bool QtQuickAdapter::process(const Envelope &env) +{ + if (std::dynamic_pointer_cast(env.sender)) { + switch (env.sender->value()) { + case TerminalUsers::Settings: + return processSettings(env); + case TerminalUsers::Blockchain: + return processBlockchain(env); + case TerminalUsers::Signer: + return processSigner(env); + case TerminalUsers::Wallets: + return processWallets(env); + default: break; + } + } + return true; +} + +bool QtQuickAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (std::dynamic_pointer_cast(env.sender)) { + switch (env.sender->value()) { + case TerminalUsers::System: + return processAdminMessage(env); + case TerminalUsers::Settings: + return processSettings(env); + case TerminalUsers::Blockchain: + return processBlockchain(env); + case TerminalUsers::Signer: + return processSigner(env); + case TerminalUsers::Wallets: + return processWallets(env); + default: break; + } + } + return false; +} + +bool QtQuickAdapter::processSettings(const Envelope &env) +{ + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kGetResponse: + return processSettingsGetResponse(msg.get_response()); + case SettingsMessage::kSettingsUpdated: + return processSettingsGetResponse(msg.settings_updated()); + case SettingsMessage::kState: + return processSettingsState(msg.state()); + case SettingsMessage::kArmoryServers: + return processArmoryServers(msg.armory_servers()); + default: break; + } + return true; +} + +bool QtQuickAdapter::processSettingsGetResponse(const SettingsMessage_SettingsResponse &response) +{ + std::map settings; + for (const auto &setting : response.responses()) { + switch (setting.request().index()) { + case SetIdx_GUI_MainGeom: { + QRect mainGeometry(setting.rect().left(), setting.rect().top() + , setting.rect().width(), setting.rect().height()); + if (splashScreen_) { +// const auto ¤tDisplay = getDisplay(mainGeometry.center()); +// auto splashGeometry = splashScreen_->geometry(); +// splashGeometry.moveCenter(currentDisplay->geometry().center()); +// QMetaObject::invokeMethod(splashScreen_, [ss=splashScreen_, splashGeometry] { +// ss->setGeometry(splashGeometry); +// }); + } + } + break; + + case SetIdx_Initialized: + if (!setting.b()) { +#ifdef _WIN32 + // Read registry value in case it was set with installer. Could be used only on Windows for now. + QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\blocksettle\\blocksettle"), QSettings::NativeFormat); + bool showLicense = !settings.value(QLatin1String("license_accepted"), false).toBool(); +#else + bool showLicense = true; +#endif // _WIN32 + //onResetSettings({}); + } + break; + + default: + settings[setting.request().index()] = fromResponse(setting); + break; + } + } + if (!settings.empty()) { + //TODO: propagate settings to GUI + } + return true; +} + +bool QtQuickAdapter::processSettingsState(const SettingsMessage_SettingsResponse& response) +{ + ApplicationSettings::State state; + for (const auto& setting : response.responses()) { + state[static_cast(setting.request().index())] = + fromResponse(setting); + } + //TODO: process setting + return true; +} + +bool QtQuickAdapter::processArmoryServers(const SettingsMessage_ArmoryServers& response) +{ + QList servers; + for (const auto& server : response.servers()) { + servers << ArmoryServer{ QString::fromStdString(server.server_name()) + , static_cast(server.network_type()) + , QString::fromStdString(server.server_address()) + , std::stoi(server.server_port()), QString::fromStdString(server.server_key()) + , SecureBinaryData::fromString(server.password()) + , server.run_locally(), server.one_way_auth() }; + } + logger_->debug("[{}] {} servers, cur: {}, conn: {}", __func__, servers.size() + , response.idx_current(), response.idx_connected()); + //TODO + return true; +} + +bool QtQuickAdapter::processAdminMessage(const Envelope &env) +{ + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse admin msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case AdministrativeMessage::kComponentCreated: + switch (static_cast(msg.component_created())) { + case TerminalUsers::Unknown: + case TerminalUsers::API: + case TerminalUsers::Settings: + break; + default: + createdComponents_.insert(msg.component_created()); + break; + } + break; + case AdministrativeMessage::kComponentLoading: { + std::lock_guard lock(mutex_); + loadingComponents_.insert(msg.component_loading()); + break; + } + default: break; + } + updateSplashProgress(); + return true; +} + +bool QtQuickAdapter::processBlockchain(const Envelope &env) +{ + ArmoryMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[QtGuiAdapter::processBlockchain] failed to parse msg #{}" + , env.foreignId()); + if (!env.receiver) { + logger_->debug("[{}] no receiver", __func__); + } + return true; + } + switch (msg.data_case()) { + case ArmoryMessage::kLoading: + loadingComponents_.insert(env.sender->value()); + updateSplashProgress(); + break; + case ArmoryMessage::kStateChanged: + armoryState_ = msg.state_changed().state(); + blockNum_ = msg.state_changed().top_block(); + //TODO + break; + case ArmoryMessage::kNewBlock: + blockNum_ = msg.new_block().top_block(); + //TODO + break; + case ArmoryMessage::kWalletRegistered: + if (msg.wallet_registered().success() && msg.wallet_registered().wallet_id().empty()) { + walletsReady_ = true; + //TODO + } + break; + case ArmoryMessage::kAddressHistory: + return processAddressHist(msg.address_history()); + case ArmoryMessage::kFeeLevelsResponse: + return processFeeLevels(msg.fee_levels_response()); + case ArmoryMessage::kZcReceived: + return processZC(msg.zc_received()); + case ArmoryMessage::kZcInvalidated: + return processZCInvalidated(msg.zc_invalidated()); + default: break; + } + return true; +} + +bool QtQuickAdapter::processSigner(const Envelope &env) +{ + SignerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[QtGuiAdapter::processSigner] failed to parse msg #{}" + , env.foreignId()); + if (!env.receiver) { + logger_->debug("[{}] no receiver", __func__); + } + return true; + } + switch (msg.data_case()) { + case SignerMessage::kState: + signerState_ = msg.state().code(); + signerDetails_ = msg.state().text(); + //TODO + break; + case SignerMessage::kNeedNewWalletPrompt: + createWallet(true); + break; + case SignerMessage::kSignTxResponse: + return processSignTX(msg.sign_tx_response()); + case SignerMessage::kWalletDeleted: + { + const auto& itWallet = hdWallets_.find(msg.wallet_deleted()); + bs::sync::WalletInfo wi; + if (itWallet == hdWallets_.end()) { + wi.ids.push_back(msg.wallet_deleted()); + } else { + wi = itWallet->second; + } + //TODO + } + break; + default: break; + } + return true; +} + +bool QtQuickAdapter::processWallets(const Envelope &env) +{ + WalletsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case WalletsMessage::kLoading: + loadingComponents_.insert(env.sender->value()); + updateSplashProgress(); + break; + + case WalletsMessage::kWalletLoaded: { + const auto &wi = bs::sync::WalletInfo::fromCommonMsg(msg.wallet_loaded()); + processWalletLoaded(wi); + } + break; + + case WalletsMessage::kHdWallet: { + const auto &hdw = bs::sync::HDWalletData::fromCommonMessage(msg.hd_wallet()); + //TODO + } + break; + + case WalletsMessage::kWalletDeleted: { + const auto& wi = bs::sync::WalletInfo::fromCommonMsg(msg.wallet_deleted()); + //TODO + } + break; + + case WalletsMessage::kWalletAddresses: { + std::vector addresses; + for (const auto &addr : msg.wallet_addresses().addresses()) { + try { + addresses.push_back({ std::move(bs::Address::fromAddressString(addr.address())) + , addr.index(), addr.wallet_id() }); + } + catch (const std::exception &) {} + } + const auto& walletId = msg.wallet_addresses().wallet_id(); + auto itReq = needChangeAddrReqs_.find(env.responseId()); + if (itReq != needChangeAddrReqs_.end()) { + //TODO + needChangeAddrReqs_.erase(itReq); + } + else { + //TODO + } + } + break; + + case WalletsMessage::kAddrComments: { + std::map comments; + for (const auto &addrComment : msg.addr_comments().comments()) { + try { + comments[bs::Address::fromAddressString(addrComment.address())] = addrComment.comment(); + } + catch (const std::exception &) {} + } + //TODO + } + break; + case WalletsMessage::kWalletData: + return processWalletData(env.responseId(), msg.wallet_data()); + case WalletsMessage::kWalletBalances: + return processWalletBalances(env, msg.wallet_balances()); + case WalletsMessage::kTxDetailsResponse: + return processTXDetails(env.responseId(), msg.tx_details_response()); + case WalletsMessage::kWalletsListResponse: + return processWalletsList(msg.wallets_list_response()); + case WalletsMessage::kUtxos: + return processUTXOs(msg.utxos()); + case WalletsMessage::kReservedUtxos: + return processReservedUTXOs(msg.reserved_utxos()); + case WalletsMessage::kWalletChanged: + if (walletsReady_) { + //TODO: onNeedLedgerEntries({}); + } + break; + case WalletsMessage::kLedgerEntries: + return processLedgerEntries(msg.ledger_entries()); + default: break; + } + return true; +} + +void QtQuickAdapter::updateStates() +{ + //TODO +} + +//#define DEBUG_LOADING_PROGRESS +void QtQuickAdapter::updateSplashProgress() +{ + std::lock_guard lock(mutex_); + if (createdComponents_.empty()) { + if (splashScreen_) { + QMetaObject::invokeMethod(splashScreen_, [this] { + QTimer::singleShot(100, [this] { + splashScreen_->hide(); + splashScreen_->deleteLater(); + splashScreen_ = nullptr; + }); + }); + } + return; + } + auto percent = unsigned(100 * loadingComponents_.size() / createdComponents_.size()); +#ifdef DEBUG_LOADING_PROGRESS + std::string l, c; + for (const auto &lc : loadingComponents_) { + l += std::to_string(lc) + " "; + } + for (const auto &cc : createdComponents_) { + c += std::to_string(cc) + " "; + } + logger_->debug("[{}] {} / {} = {}%", __func__, l, c, percent); +#endif + if (splashScreen_) { + QMetaObject::invokeMethod(splashScreen_, [this, percent] { + splashScreen_->SetProgress(percent); + }); + } + if (percent >= 100) { + splashProgressCompleted(); + } +} + +void QtQuickAdapter::splashProgressCompleted() +{ + { + std::lock_guard lock(mutex_); + loadingDone_ = true; + loadingComponents_.clear(); + createdComponents_.clear(); + } + if (!splashScreen_) { + return; + } + QMetaObject::invokeMethod(splashScreen_, [this] { + auto window = qobject_cast(rootObj_); + if (window) { + window->show(); + } + else { + logger_->error("[QtQuickAdapter::splashProgressCompleted] no main window found"); + } + QTimer::singleShot(100, [this] { + splashScreen_->hide(); + splashScreen_->deleteLater(); + splashScreen_ = nullptr; + }); + }); +} + +void QtQuickAdapter::requestInitialSettings() +{ + SettingsMessage msg; + auto msgReq = msg.mutable_get_request(); + auto setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_GUI_MainGeom); + setReq->set_type(SettingType_Rect); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_Initialized); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_GUI_MainTab); + setReq->set_type(SettingType_Int); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_ShowInfoWidget); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_AdvancedTXisDefault); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_CloseToTray); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_Environment); + setReq->set_type(SettingType_Int); + + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtQuickAdapter::createWallet(bool primary) +{ + logger_->debug("[{}] primary: {}", __func__, primary); +} + +void QtQuickAdapter::processWalletLoaded(const bs::sync::WalletInfo &wi) +{ + hdWallets_[*wi.ids.cbegin()] = wi; + //TODO +} + +bool QtQuickAdapter::processWalletData(uint64_t msgId + , const WalletsMessage_WalletData& response) +{ + const auto& itWallet = walletGetMap_.find(msgId); + if (itWallet == walletGetMap_.end()) { + return true; + } + const auto& walletId = itWallet->second; + const auto& walletData = bs::sync::WalletData::fromCommonMessage(response); + //TODO + { + walletGetMap_.erase(itWallet); + return true; + } + return false; +} + +bool QtQuickAdapter::processWalletBalances(const bs::message::Envelope & + , const WalletsMessage_WalletBalances &response) +{ + bs::sync::WalletBalanceData wbd; + wbd.id = response.wallet_id(); + wbd.balTotal = response.total_balance(); + wbd.balSpendable = response.spendable_balance(); + wbd.balUnconfirmed = response.unconfirmed_balance(); + wbd.nbAddresses = response.nb_addresses(); + for (const auto &addrBal : response.address_balances()) { + wbd.addrBalances.push_back({ BinaryData::fromString(addrBal.address()) + , addrBal.tx_count(), (int64_t)addrBal.total_balance(), (int64_t)addrBal.spendable_balance() + , (int64_t)addrBal.unconfirmed_balance() }); + } + //TODO + return true; +} + +bool QtQuickAdapter::processTXDetails(uint64_t msgId, const WalletsMessage_TXDetailsResponse &response) +{ + std::vector txDetails; + for (const auto &resp : response.responses()) { + bs::sync::TXWalletDetails txDet{ BinaryData::fromString(resp.tx_hash()), resp.wallet_id() + , resp.wallet_name(), static_cast(resp.wallet_type()) + , resp.wallet_symbol(), static_cast(resp.direction()) + , resp.comment(), resp.valid(), resp.amount() }; + if (!response.error_msg().empty()) { + txDet.comment = response.error_msg(); + } + + const auto &ownTxHash = BinaryData::fromString(resp.tx_hash()); + try { + if (!resp.tx().empty()) { + Tx tx(BinaryData::fromString(resp.tx())); + if (tx.isInitialized()) { + txDet.tx = std::move(tx); + } + } + } catch (const std::exception &e) { + logger_->warn("[QtGuiAdapter::processTXDetails] TX deser error: {}", e.what()); + } + for (const auto &addrStr : resp.out_addresses()) { + try { + txDet.outAddresses.push_back(std::move(bs::Address::fromAddressString(addrStr))); + } catch (const std::exception &e) { + logger_->warn("[QtGuiAdapter::processTXDetails] out deser error: {}", e.what()); + } + } + for (const auto &inAddr : resp.input_addresses()) { + try { + txDet.inputAddresses.push_back({ bs::Address::fromAddressString(inAddr.address()) + , inAddr.value(), inAddr.value_string(), inAddr.wallet_name() + , static_cast(inAddr.script_type()) + , BinaryData::fromString(inAddr.out_hash()), inAddr.out_index() }); + } catch (const std::exception &e) { + logger_->warn("[QtGuiAdapter::processTXDetails] input deser error: {}", e.what()); + } + } + for (const auto &outAddr : resp.output_addresses()) { + try { + txDet.outputAddresses.push_back({ bs::Address::fromAddressString(outAddr.address()) + , outAddr.value(), outAddr.value_string(), outAddr.wallet_name() + , static_cast(outAddr.script_type()) + , BinaryData::fromString(outAddr.out_hash()), outAddr.out_index() }); + } catch (const std::exception &) { // OP_RETURN data for valueStr + txDet.outputAddresses.push_back({ bs::Address{} + , outAddr.value(), outAddr.address(), outAddr.wallet_name() + , static_cast(outAddr.script_type()), ownTxHash + , outAddr.out_index() }); + } + } + try { + txDet.changeAddress = { bs::Address::fromAddressString(resp.change_address().address()) + , resp.change_address().value(), resp.change_address().value_string() + , resp.change_address().wallet_name() + , static_cast(resp.change_address().script_type()) + , BinaryData::fromString(resp.change_address().out_hash()) + , resp.change_address().out_index() }; + } + catch (const std::exception &) {} + txDetails.emplace_back(std::move(txDet)); + } + if (!response.responses_size() && !response.error_msg().empty()) { + bs::sync::TXWalletDetails txDet; + txDet.comment = response.error_msg(); + txDetails.emplace_back(std::move(txDet)); + } + const auto& itZC = newZCs_.find(msgId); + if (itZC != newZCs_.end()) { + newZCs_.erase(itZC); + //TODO + } + else { + //TODO + } + return true; +} + +bool QtQuickAdapter::processLedgerEntries(const LedgerEntries &response) +{ + std::vector entries; + for (const auto &entry : response.entries()) { + bs::TXEntry txEntry; + txEntry.txHash = BinaryData::fromString(entry.tx_hash()); + txEntry.value = entry.value(); + txEntry.blockNum = entry.block_num(); + txEntry.txTime = entry.tx_time(); + txEntry.isRBF = entry.rbf(); + txEntry.isChainedZC = entry.chained_zc(); + txEntry.nbConf = entry.nb_conf(); + for (const auto &walletId : entry.wallet_ids()) { + txEntry.walletIds.insert(walletId); + } + for (const auto &addrStr : entry.addresses()) { + try { + const auto &addr = bs::Address::fromAddressString(addrStr); + txEntry.addresses.push_back(addr); + } + catch (const std::exception &) {} + } + entries.push_back(std::move(txEntry)); + } + //TODO + return true; +} + + +bool QtQuickAdapter::processAddressHist(const ArmoryMessage_AddressHistory& response) +{ + bs::Address addr; + try { + addr = std::move(bs::Address::fromAddressString(response.address())); + } + catch (const std::exception& e) { + logger_->error("[{}] invalid address: {}", __func__, e.what()); + return true; + } + std::vector entries; + for (const auto& entry : response.entries()) { + bs::TXEntry txEntry; + txEntry.txHash = BinaryData::fromString(entry.tx_hash()); + txEntry.value = entry.value(); + txEntry.blockNum = entry.block_num(); + txEntry.txTime = entry.tx_time(); + txEntry.isRBF = entry.rbf(); + txEntry.isChainedZC = entry.chained_zc(); + txEntry.nbConf = entry.nb_conf(); + for (const auto& walletId : entry.wallet_ids()) { + txEntry.walletIds.insert(walletId); + } + for (const auto& addrStr : entry.addresses()) { + try { + const auto& addr = bs::Address::fromAddressString(addrStr); + txEntry.addresses.push_back(addr); + } + catch (const std::exception&) {} + } + entries.push_back(std::move(txEntry)); + } + //TODO + return true; +} + +bool QtQuickAdapter::processFeeLevels(const ArmoryMessage_FeeLevelsResponse& response) +{ + std::map feeLevels; + for (const auto& pair : response.fee_levels()) { + feeLevels[pair.level()] = pair.fee(); + } + //TODO + return true; +} + +bool QtQuickAdapter::processWalletsList(const WalletsMessage_WalletsListResponse& response) +{ + std::vector wallets; + for (const auto& wallet : response.wallets()) { + wallets.push_back(bs::sync::HDWalletData::fromCommonMessage(wallet)); + } + //TODO + return true; +} + +bool QtQuickAdapter::processUTXOs(const WalletsMessage_UtxoListResponse& response) +{ + std::vector utxos; + for (const auto& serUtxo : response.utxos()) { + UTXO utxo; + utxo.unserialize(BinaryData::fromString(serUtxo)); + utxos.push_back(std::move(utxo)); + } + //TODO + return true; +} + +bool QtQuickAdapter::processSignTX(const BlockSettle::Common::SignerMessage_SignTxResponse& response) +{ + //TODO + return true; +} + +bool QtQuickAdapter::processZC(const BlockSettle::Common::ArmoryMessage_ZCReceived& zcs) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_tx_details_request(); + for (const auto& zcEntry : zcs.tx_entries()) { + auto txReq = msgReq->add_requests(); + txReq->set_tx_hash(zcEntry.tx_hash()); + if (zcEntry.wallet_ids_size() > 0) { + txReq->set_wallet_id(zcEntry.wallet_ids(0)); + } + txReq->set_value(zcEntry.value()); + } + const auto msgId = pushRequest(user_, userWallets_, msg.SerializeAsString()); + if (!msgId) { + return false; + } + newZCs_.insert(msgId); + return true; +} + +bool QtQuickAdapter::processZCInvalidated(const ArmoryMessage_ZCInvalidated& zcInv) +{ + std::vector txHashes; + for (const auto& hashStr : zcInv.tx_hashes()) { + txHashes.push_back(BinaryData::fromString(hashStr)); + } + //TODO + return true; +} + +bool QtQuickAdapter::processReservedUTXOs(const WalletsMessage_ReservedUTXOs& response) +{ + std::vector utxos; + for (const auto& utxoSer : response.utxos()) { + UTXO utxo; + utxo.unserialize(BinaryData::fromString(utxoSer)); + utxos.push_back(std::move(utxo)); + } + //TODO + return true; +} diff --git a/GUI/QtQuick/QtQuickAdapter.h b/GUI/QtQuick/QtQuickAdapter.h new file mode 100644 index 000000000..03312cd93 --- /dev/null +++ b/GUI/QtQuick/QtQuickAdapter.h @@ -0,0 +1,146 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2022, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef QT_QUICK_ADAPTER_H +#define QT_QUICK_ADAPTER_H + +#include +#include +#include "Address.h" +#include "ApiAdapter.h" +#include "Wallets/SignContainer.h" +#include "ThreadSafeClasses.h" +#include "UiUtils.h" + +namespace bs { + namespace gui { + namespace qt { + class MainWindow; + } + } +} +namespace BlockSettle { + namespace Common { + class ArmoryMessage_AddressHistory; + class ArmoryMessage_FeeLevelsResponse; + class ArmoryMessage_ZCInvalidated; + class ArmoryMessage_ZCReceived; + class LedgerEntries; + class OnChainTrackMessage_AuthAddresses; + class OnChainTrackMessage_AuthState; + class SignerMessage_SignTxResponse; + class WalletsMessage_AuthKey; + class WalletsMessage_ReservedUTXOs; + class WalletsMessage_TXDetailsResponse; + class WalletsMessage_UtxoListResponse; + class WalletsMessage_WalletBalances; + class WalletsMessage_WalletData; + class WalletsMessage_WalletsListResponse; + } + namespace Terminal { + class AssetsMessage_Balance; + class AssetsMessage_SubmittedAuthAddresses; + class BsServerMessage_LoginResult; + class BsServerMessage_Orders; + class BsServerMessage_StartLoginResult; + class MatchingMessage_LoggedIn; + class Quote; + class QuoteCancelled; + class IncomingRFQ; + class MatchingMessage_Order; + class MktDataMessage_Prices; + class SettlementMessage_FailedSettlement; + class SettlementMessage_MatchedQuote; + class SettlementMessage_PendingSettlement; + class SettlementMessage_SettlementIds; + class SettingsMessage_ArmoryServers; + class SettingsMessage_SettingsResponse; + class SettingsMessage_SignerServers; + } +} + +class BSTerminalSplashScreen; + +class QtQuickAdapter : public QObject, public ApiBusAdapter, public bs::MainLoopRuner +{ + Q_OBJECT + friend class GuiThread; +public: + QtQuickAdapter(const std::shared_ptr &); + ~QtQuickAdapter() override; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "QtQuick"; } + + void run(int &argc, char **argv) override; + +private: + bool processSettings(const bs::message::Envelope &); + bool processSettingsGetResponse(const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + bool processSettingsState(const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + bool processArmoryServers(const BlockSettle::Terminal::SettingsMessage_ArmoryServers&); + bool processAdminMessage(const bs::message::Envelope &); + bool processBlockchain(const bs::message::Envelope &); + bool processSigner(const bs::message::Envelope &); + bool processWallets(const bs::message::Envelope &); + + void requestInitialSettings(); + void updateSplashProgress(); + void splashProgressCompleted(); + void updateStates(); + + void createWallet(bool primary); + + void processWalletLoaded(const bs::sync::WalletInfo &); + bool processWalletData(const uint64_t msgId + , const BlockSettle::Common::WalletsMessage_WalletData&); + bool processWalletBalances(const bs::message::Envelope & + , const BlockSettle::Common::WalletsMessage_WalletBalances &); + bool processTXDetails(uint64_t msgId, const BlockSettle::Common::WalletsMessage_TXDetailsResponse &); + bool processLedgerEntries(const BlockSettle::Common::LedgerEntries &); + bool processAddressHist(const BlockSettle::Common::ArmoryMessage_AddressHistory&); + bool processFeeLevels(const BlockSettle::Common::ArmoryMessage_FeeLevelsResponse&); + bool processWalletsList(const BlockSettle::Common::WalletsMessage_WalletsListResponse&); + bool processUTXOs(const BlockSettle::Common::WalletsMessage_UtxoListResponse&); + bool processSignTX(const BlockSettle::Common::SignerMessage_SignTxResponse&); + bool processZC(const BlockSettle::Common::ArmoryMessage_ZCReceived&); + bool processZCInvalidated(const BlockSettle::Common::ArmoryMessage_ZCInvalidated&); + bool processReservedUTXOs(const BlockSettle::Common::WalletsMessage_ReservedUTXOs&); + +private: + std::shared_ptr logger_; + BSTerminalSplashScreen* splashScreen_{ nullptr }; + QObject* rootObj_{ nullptr }; + std::shared_ptr userSettings_, userWallets_; + std::shared_ptr userBlockchain_, userSigner_; + bool loadingDone_{ false }; + + std::recursive_mutex mutex_; + std::set createdComponents_; + std::set loadingComponents_; + int armoryState_{ -1 }; + uint32_t blockNum_{ 0 }; + int signerState_{ -1 }; + std::string signerDetails_; + bool walletsReady_{ false }; + + std::map walletGetMap_; + std::unordered_map hdWallets_; + std::set newZCs_; + + std::unordered_map assetTypes_; + std::set needChangeAddrReqs_; +}; + + +#endif // QT_QUICK_ADAPTER_H diff --git a/GUI/QtQuick/images/bs_logo.png b/GUI/QtQuick/images/bs_logo.png new file mode 100644 index 000000000..640473792 Binary files /dev/null and b/GUI/QtQuick/images/bs_logo.png differ diff --git a/GUI/QtQuick/images/full_logo.png b/GUI/QtQuick/images/full_logo.png new file mode 100644 index 000000000..bb74f3693 Binary files /dev/null and b/GUI/QtQuick/images/full_logo.png differ diff --git a/GUI/QtQuick/images/notification_critical.png b/GUI/QtQuick/images/notification_critical.png new file mode 100644 index 000000000..dc6019a4d Binary files /dev/null and b/GUI/QtQuick/images/notification_critical.png differ diff --git a/GUI/QtQuick/images/notification_info.png b/GUI/QtQuick/images/notification_info.png new file mode 100644 index 000000000..86f38d32d Binary files /dev/null and b/GUI/QtQuick/images/notification_info.png differ diff --git a/GUI/QtQuick/images/notification_question.png b/GUI/QtQuick/images/notification_question.png new file mode 100644 index 000000000..f73f3dbbf Binary files /dev/null and b/GUI/QtQuick/images/notification_question.png differ diff --git a/GUI/QtQuick/images/notification_success.png b/GUI/QtQuick/images/notification_success.png new file mode 100644 index 000000000..3592f98eb Binary files /dev/null and b/GUI/QtQuick/images/notification_success.png differ diff --git a/GUI/QtQuick/images/notification_warning.png b/GUI/QtQuick/images/notification_warning.png new file mode 100644 index 000000000..95173342c Binary files /dev/null and b/GUI/QtQuick/images/notification_warning.png differ diff --git a/GUI/QtQuick/qml/BsStyles/BSStyle.qml b/GUI/QtQuick/qml/BsStyles/BSStyle.qml new file mode 100644 index 000000000..5fcf78014 --- /dev/null +++ b/GUI/QtQuick/qml/BsStyles/BSStyle.qml @@ -0,0 +1,76 @@ +/* + +*********************************************************************************** +* Copyright (C) 2022, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +pragma Singleton +import QtQuick 2.0 + +//color names taken from http://chir.ag/projects/name-that-color + +QtObject { + readonly property color backgroundColor: "#1c2835" + readonly property color backgroundPressedColor: "#2c3845" + readonly property color backgroundModalColor: "#737373" + readonly property color backgroundModeLessColor: "#939393" + + readonly property color disabledColor: "#41484f" + readonly property color disabledTextColor: "#71787f" + readonly property color disabledBgColor: "#31383f" + + readonly property color textColor: "white" + readonly property color textPressedColor: "#3a8ab4" + readonly property color disabledHeaderColor: "#909090" + + readonly property color labelsTextColor: "#939393" + readonly property color labelsTextDisabledColor: "#454E53" + readonly property color inputsBorderColor: "#757E83" + readonly property color inputsFontColor: "white" + readonly property color inputsInvalidColor: "red" + readonly property color inputsValidColor: "green" + readonly property color inputsPendingColor: "#f6a724" + + readonly property color buttonsMainColor: "transparent" + readonly property color buttonsPressedColor: "#55000000" + readonly property color buttonsHoveredColor: "#22000000" + + readonly property color buttonsPrimaryMainColor: "#247dac" + readonly property color buttonsPrimaryPressedColor: "#22C064" + readonly property color buttonsPrimaryHoveredColor: "#449dcc" + + readonly property color buttonsUncheckedColor: "#81888f" + readonly property color buttonsBorderColor: "#247dac" + + readonly property color progressBarColor: "#22C064" + readonly property color progressBarBgColor: "black" + + readonly property color switchBgColor: "transparent" + //readonly property color switchCheckedColor: "#22C064" + readonly property color switchCheckedColor: "#247dac" + readonly property color switchOrangeColor: "#f6a724" + readonly property color switchUncheckedColor: "#b1b8bf" + readonly property color switchDisabledBgColor: disabledColor + readonly property color switchDisabledColor: disabledTextColor + + readonly property color dialogHeaderColor: "#0A1619" + readonly property color dialogTitleGreenColor: "#38C673" + readonly property color dialogTitleOrangeColor: "#f7b03a" + readonly property color dialogTitleRedColor: "#EE2249" + readonly property color dialogTitleWhiteColor: "white" + + readonly property color comboBoxBgColor: "transparent" + readonly property color comboBoxItemBgColor: "#17262b" + readonly property color comboBoxItemBgHighlightedColor: "#27363b" + readonly property color comboBoxItemTextColor: textColor + readonly property color comboBoxItemTextHighlightedColor: textColor + + readonly property color mainnetColor: "#fe9727" + readonly property color testnetColor: "#22c064" + readonly property color mainnetTextColor: "white" + readonly property color testnetTextColor: "black" +} diff --git a/GUI/QtQuick/qml/BsStyles/qmldir b/GUI/QtQuick/qml/BsStyles/qmldir new file mode 100644 index 000000000..ad71c3261 --- /dev/null +++ b/GUI/QtQuick/qml/BsStyles/qmldir @@ -0,0 +1,3 @@ +module BlockSettleStyle + +singleton BSStyle 1.0 BSStyle.qml diff --git a/GUI/QtQuick/qml/InfoBanner.qml b/GUI/QtQuick/qml/InfoBanner.qml new file mode 100644 index 000000000..7cedd5c37 --- /dev/null +++ b/GUI/QtQuick/qml/InfoBanner.qml @@ -0,0 +1,53 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 + +Loader { + id: messages + property color bgColor + + function displayMessage(message) { + messages.source = ""; + messages.source = Qt.resolvedUrl("InfoBannerComponent.qml"); + messages.item.message = message; + messages.item.bgColor = bgColor + } + + width: parent.width + anchors.bottom: parent.top + z: 5 + onLoaded: { + messages.item.state = "portrait"; + timer.running = true + messages.state = "show" + } + + Timer { + id: timer + + interval: 7000 + onTriggered: { + messages.state = "" + } + } + + states: [ + State { + name: "show" + AnchorChanges { target: messages; anchors { bottom: undefined; top: parent.top } } + PropertyChanges { target: messages; anchors.topMargin: 100 } + } + ] + + transitions: Transition { + AnchorAnimation { easing.type: Easing.OutQuart; duration: 300 } + } +} diff --git a/GUI/QtQuick/qml/InfoBannerComponent.qml b/GUI/QtQuick/qml/InfoBannerComponent.qml new file mode 100644 index 000000000..b43bad00d --- /dev/null +++ b/GUI/QtQuick/qml/InfoBannerComponent.qml @@ -0,0 +1,55 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 + +Item { + id: banner + + property alias message: messageText.text + property alias bgColor: background.color + + height: 70 + + Rectangle { + id: background + + anchors.fill: banner + smooth: true + opacity: 0.8 + } + + Text { + font.pixelSize: 20 + renderType: Text.QtRendering + width: 150 + height: 40 + id: messageText + + + anchors.fill: banner + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + color: "white" + } + + states: State { + name: "portrait" + PropertyChanges { target: banner; height: 100 } + } + + MouseArea { + anchors.fill: parent + onClicked: { + messageText.state = "" + } + } +} diff --git a/GUI/QtQuick/qml/InfoBar.qml b/GUI/QtQuick/qml/InfoBar.qml new file mode 100644 index 000000000..1a96c8cfe --- /dev/null +++ b/GUI/QtQuick/qml/InfoBar.qml @@ -0,0 +1,74 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.12 +import QtQuick.Layouts 1.3 + +import "BsStyles" + +Item { + id: infoBarRoot + height: 30 + + property bool showChangeApplyMessage: false + + RowLayout { + anchors.fill: parent + spacing: 10 + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Text { + visible: infoBarRoot.showChangeApplyMessage + anchors { + fill: parent + leftMargin: 10 + } + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + text: qsTr("Changes will take effect after the application is restarted.") + color: BSStyle.inputsPendingColor + } + } + + Rectangle { + id: netLabel + property bool bInitAsTestNet: signerSettings.testNet + + radius: 5 + color: bInitAsTestNet ? BSStyle.testnetColor : BSStyle.mainnetColor + width: 100 + height: 20 + Layout.alignment: Qt.AlignVCenter + + Text { + text: netLabel.bInitAsTestNet ? qsTr("Testnet") : qsTr("Mainnet") + color: netLabel.bInitAsTestNet ? BSStyle.testnetTextColor : BSStyle.mainnetTextColor + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + Component.onCompleted: { + bInitAsTestNet = signerSettings.testNet + } + } + } + + Rectangle { + height: 1 + width: parent.width + color: Qt.rgba(1, 1, 1, 0.1) + anchors.bottom: parent.bottom + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomButton.qml b/GUI/QtQuick/qml/StyledControls/CustomButton.qml new file mode 100644 index 000000000..80adca47a --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomButton.qml @@ -0,0 +1,94 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +Button { + id: control + property bool capitalize: true + property bool primary: false + text: parent.text + leftPadding: 15 + rightPadding: 15 + anchors.margins: 5 + + contentItem: Text { + text: control.text + opacity: enabled ? 1.0 : 0.3 + color: BSStyle.textColor + font.capitalization: capitalize ? Font.AllUppercase : Font.MixedCase + font.pixelSize: 11 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + background: Rectangle { + id: rect + implicitWidth: 110 + implicitHeight: 35 + opacity: primary ? 1 : (control.enabled ? 1 : 0.3) + border.color: BSStyle.buttonsBorderColor + color: primary ? BSStyle.buttonsPrimaryMainColor : (control.highlighted ? BSStyle.buttonsPrimaryMainColor : BSStyle.buttonsMainColor) + border.width: primary ? 0 : 1 + } + + states: [ + State { + name: "" + PropertyChanges { + target: rect + opacity: primary ? 1 : (control.enabled ? 1 : 0.3) + color: primary ? BSStyle.buttonsPrimaryMainColor : (control.highlighted ? BSStyle.buttonsPrimaryMainColor : BSStyle.buttonsMainColor) + } + }, + State { + name: "pressed" + when: control.pressed + PropertyChanges { + target: rect + opacity: primary ? 0.7 : (control.enabled ? 1 : 0.3) + color: primary ? BSStyle.buttonsPrimaryMainColor : (control.highlighted ? BSStyle.buttonsPrimaryPressedColor : BSStyle.buttonsPressedColor) + } + }, + State { + name: "hovered" + when: control.hovered + PropertyChanges { + target: rect + opacity: primary ? 0.85 : (control.enabled ? 1 : 0.3) + color: primary ? BSStyle.buttonsPrimaryMainColor : (control.highlighted ? BSStyle.buttonsPrimaryHoveredColor : BSStyle.buttonsHoveredColor) + } + }, + State { + name: "disabled" + when: !control.enabled + PropertyChanges { + target: rect + opacity: primary ? 0.3 : (control.enabled ? 1 : 0.3) + color: primary ? BSStyle.buttonsPrimaryMainColor : "gray" + } + } + ] + + transitions: [ + Transition { + from: ""; to: "hovered" + ColorAnimation { duration: 100 } + }, + Transition { + from: "*"; to: "pressed" + ColorAnimation { duration: 10 } + } + ] +} + diff --git a/BlockSettleUILib/CCAmountValidator.cpp b/GUI/QtQuick/qml/StyledControls/CustomButtonBar.qml similarity index 62% rename from BlockSettleUILib/CCAmountValidator.cpp rename to GUI/QtQuick/qml/StyledControls/CustomButtonBar.qml index 3fec14e8d..763b98c91 100644 --- a/BlockSettleUILib/CCAmountValidator.cpp +++ b/GUI/QtQuick/qml/StyledControls/CustomButtonBar.qml @@ -8,13 +8,13 @@ ********************************************************************************** */ -#include "CCAmountValidator.h" +import QtQuick 2.9 +import QtQuick.Controls 2.3 -#include "UiUtils.h" +import "../BsStyles" -CCAmountValidator::CCAmountValidator(QObject* parent) - : CustomDoubleValidator(parent) -{ - setBottom(0.0); - setDecimals(UiUtils::GetAmountPrecisionCC()); -} \ No newline at end of file +Rectangle { + width: parent.width + color:"#55000000" + height: 45 +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomCheckBox.qml b/GUI/QtQuick/qml/StyledControls/CustomCheckBox.qml new file mode 100644 index 000000000..49d89f54b --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomCheckBox.qml @@ -0,0 +1,46 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +CheckBox { + id: control + text: parent.text + + indicator: Rectangle { + implicitWidth: 16 + implicitHeight: 16 + y: parent.height / 2 - height / 2 + radius: 0 + border.color: control.checked ? BSStyle.buttonsBorderColor : BSStyle.buttonsUncheckedColor + color: "transparent" + + Rectangle { + width: 8 + height: 8 + x: 4 + y: 4 + radius: 0 + color: control.checked ? BSStyle.buttonsPrimaryMainColor : BSStyle.buttonsUncheckedColor + visible: control.checked + } + } + + contentItem: Text { + text: control.text + font.pixelSize: 11 + opacity: enabled ? 1.0 : 0.3 + color: control.checked ? BSStyle.textColor : BSStyle.buttonsUncheckedColor + verticalAlignment: Text.AlignVCenter + leftPadding: control.indicator.width + control.spacing + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomComboBox.qml b/GUI/QtQuick/qml/StyledControls/CustomComboBox.qml new file mode 100644 index 000000000..9f1faec97 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomComboBox.qml @@ -0,0 +1,110 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +ComboBox { + id: control + spacing: 3 + rightPadding: 30 // workaround to decrease width of TextInput + property alias maximumLength: input.maximumLength + + contentItem: TextInput { + id: input + text: control.displayText + font: control.font + color: { control.enabled ? BSStyle.comboBoxItemTextHighlightedColor : BSStyle.disabledTextColor } + leftPadding: 7 + rightPadding: control.indicator.width + control.spacing + verticalAlignment: Text.AlignVCenter + clip: true + readOnly: !editable + validator: control.validator + } + + indicator: Canvas { + id: canvas + x: control.width - width + y: control.topPadding + (control.availableHeight - height) / 2 + width: 30 + height: 8 + contextType: "2d" + + Connections { + target: control + onPressedChanged: canvas.requestPaint() + } + + onPaint: { + context.reset(); + context.moveTo(0, 0); + context.lineTo(8,8); + context.lineTo(16, 0); + context.lineTo(15, 0); + context.lineTo(8,7); + context.lineTo(1, 0); + context.closePath(); + context.fillStyle = BSStyle.comboBoxItemTextHighlightedColor; + context.fill(); + } + } + + background: Rectangle { + implicitWidth: 120 + color: { control.enabled ? BSStyle.comboBoxBgColor : BSStyle.disabledBgColor } + implicitHeight: 25 + border.color: { control.enabled ? BSStyle.inputsBorderColor : BSStyle.disabledColor } + border.width: control.visualFocus ? 2 : 1 + radius: 2 + } + + delegate: ItemDelegate { + width: control.width + id: menuItem + + contentItem: Text { + text: modelData + color: menuItem.highlighted ? BSStyle.comboBoxItemTextColor : BSStyle.comboBoxItemTextHighlightedColor + font: control.font + elide: Text.ElideNone + verticalAlignment: Text.AlignVCenter + } + highlighted: control.highlightedIndex === index + + background: Rectangle { + color: menuItem.highlighted ? BSStyle.comboBoxItemBgHighlightedColor : BSStyle.comboBoxItemBgColor + } + } + + popup: Popup { + y: control.height - 1 + width: control.width + implicitHeight: contentItem.implicitHeight + padding: 1 + + contentItem: ListView { + clip: true + implicitHeight: contentHeight + model: control.popup.visible ? control.delegateModel : null + currentIndex: control.highlightedIndex + + ScrollIndicator.vertical: ScrollIndicator { } + } + + background: Rectangle { + color: BSStyle.comboBoxItemBgColor + border.color: BSStyle.inputsBorderColor + radius: 0 + } + } +} + diff --git a/BlockSettleUILib/CCAmountValidator.h b/GUI/QtQuick/qml/StyledControls/CustomContainer.qml similarity index 51% rename from BlockSettleUILib/CCAmountValidator.h rename to GUI/QtQuick/qml/StyledControls/CustomContainer.qml index 154bb193f..cc4df0ef5 100644 --- a/BlockSettleUILib/CCAmountValidator.h +++ b/GUI/QtQuick/qml/StyledControls/CustomContainer.qml @@ -8,18 +8,18 @@ ********************************************************************************** */ -#ifndef __CC_AMOUNT_VALIDATOR_H__ -#define __CC_AMOUNT_VALIDATOR_H__ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" -#include "CustomControls/CustomDoubleValidator.h" +Pane { + width: parent.width + height: parent.height -class CCAmountValidator : public CustomDoubleValidator -{ -Q_OBJECT + Rectangle { + color: "black" + width: parent.width + height: parent.height + } +} -public: - CCAmountValidator(QObject* parent); - ~CCAmountValidator() noexcept override = default; -}; - -#endif // __CC_AMOUNT_VALIDATOR_H__ diff --git a/GUI/QtQuick/qml/StyledControls/CustomContextMenu.qml b/GUI/QtQuick/qml/StyledControls/CustomContextMenu.qml new file mode 100644 index 000000000..0af639061 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomContextMenu.qml @@ -0,0 +1,61 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +MouseArea { + hoverEnabled: true + acceptedButtons: Qt.RightButton + cursorShape: Qt.IBeamCursor + onClicked: { + if (mouse.button === Qt.RightButton) { + let selectStart = root.selectionStart + let selectEnd = root.selectionEnd + let curPos = root.cursorPosition + contextMenu.popup() + root.cursorPosition = curPos + root.select(selectStart,selectEnd) + } + } + onPressAndHold: { + if (mouse.source === Qt.MouseEventNotSynthesized) { + let selectStart = root.selectionStart + let selectEnd = root.selectionEnd + let curPos = root.cursorPosition + contextMenu.popup() + root.cursorPosition = curPos + root.select(selectStart,selectEnd) + } + } + + Menu { + id: contextMenu + MenuItem { + text: qsTr("Cut") + onTriggered: { + root.cut() + } + } + MenuItem { + text: qsTr("Copy") + onTriggered: { + root.copy() + } + } + MenuItem { + text: qsTr("Paste") + onTriggered: { + root.paste() + } + } + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomDialog.qml b/GUI/QtQuick/qml/StyledControls/CustomDialog.qml new file mode 100644 index 000000000..9995b302e --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomDialog.qml @@ -0,0 +1,287 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.0 +import QtQuick.Window 2.1 +import com.blocksettle.QmlFactory 1.0 +import QtQuick.Controls 1.4 as FirstControl + +import "../BsStyles" +import "../BsControls" +import "../js/helper.js" as JsHelper + +CustomDialogWindow { + id: root + + property bool isPrepared: false + property bool acceptable: false + property bool rejectable: false + property bool abortConfirmation: false + property int abortBoxType + + property int cContentHeight: customContentContainer.height + property int cFooterHeight: customFooterContainer.height + property int cHeaderHeight: customHeaderContainer.height + + property alias customContentContainer: customContentContainer + property alias customFooterContainer: customFooterContainer + property alias customHeaderContainer: customHeaderContainer + property alias runSpinner: busyIndicator.running + +// onCContentHeightChanged: { +// console.log("onCContentHeightChanged " + root + " " + cContentHeight) +// } + +// onCFooterHeightChanged: { +// console.log("onCFooterHeightChanged " + root + " " + cFooterHeight) +// } + +// onCHeaderHeightChanged: { +// console.log("onCHeaderHeightChanged " + root + " " + cHeaderHeight) +// } + + /////////////////// + // suggested to use these functions to close dialog popup with animation + // dialog will be rejected on animatin finished + // or after next dialog in chain will send dialogsChainFinished signal + signal bsAccepted() + signal bsRejected() + + function acceptAnimated(){ + bsAccepted() + closeTimer.start() + closeAnimation.start() + } + + function rejectAnimated(){ + bsRejected() + closeTimer.start() + closeAnimation.start() + } + + function closeAnimated(result){ + if (result) acceptAnimated() + else rejectAnimated() + } + + function hideMainWindow() { + if (applyDialogClosing()) { + rejectAnimated(); + } + } + + // override this function where needed + function applyDialogClosing() { + return true; + } + + property int animationDuration: 100 + + default property alias cContentItem: customContentContainer.data + property alias cHeaderItem: customHeaderContainer.data + property alias cFooterItem: customFooterContainer.data + + signal enterPressed() + + // this signal used in light mode to inform mainwindow if size of dialog is changed + // (for example if it's multipage dialog, or another popup doalog shown above current + signal sizeChanged(int w, int h) + + onWidthChanged: { + //console.log("CustomDialog.qml onWidthChanged " + root + " " + root.width + " " + root.height) + + if (root.width > Screen.desktopAvailableWidth) { + //console.log("CustomDialog.qml Screen width fix") + root.width = Screen.desktopAvailableWidth - 16 + } + sizeChanged(root.width, root.height) + } + onHeightChanged: { + //console.log("CustomDialog.qml onHeightChanged " + root + " " + root.width + " " + root.height) + + if (root.height > Screen.desktopAvailableHeight) { + //console.log("CustomDialog.qml Screen height fix") + let h = qmlFactory.titleBarHeight() + 16 // + extra window margins + root.height = Screen.desktopAvailableHeight - h + } + sizeChanged(root.width, root.height) + } + + //////////////////////////// + /// Dialogs chain management + + // if isNextChainDialogSet then listen next dialog for dialogsChainFinished + property bool isNextChainDialogSet: false + property var nextChainDialog: ({}) + + // when some dialog call second one we should listen second dialog for finished signal + function setNextChainDialog(dialog) { + isNextChainDialogSet = true + nextChainDialog = dialog + nextChainDialogChangedOverloaded(dialog) + dialog.dialogsChainFinished.connect(function(){ + dialogsChainFinished() + reject() + }) + dialog.nextChainDialogChangedOverloaded.connect(function(nextDialog){ + nextChainDialogChangedOverloaded(nextDialog) + }) + } + + signal nextChainDialogChangedOverloaded(var nextDialog) + + // emitted if this is signle dialog and it finished or if dioalgs chain finished + signal dialogsChainFinished() + + Component.onCompleted: { + cContentItem.parent = customContentContainer + cHeaderItem.parent = customHeaderContainer + cFooterItem.parent = customFooterContainer + } + + header: Item{} + footer: Item{} + + onClosed: { + if (!isNextChainDialogSet) { + root.destroy() + } + else { + dialogsChainFinished.connect(function(){ root.destroy() }) + } + } + + onOpened: PropertyAnimation { + id: showAnimation + target: root + property: "opacity"; + duration: animationDuration; + from: 0; to: 1 + } + + //onAboutToHide: closeAnimation + onBsAccepted: closeAnimation + onBsRejected: closeAnimation + + + PropertyAnimation { + id: closeAnimation + target: root + property: "opacity"; + duration: animationDuration; + from: 1; to: 0 + } + + Timer { + // used to close dialog when close animation completed + id: closeTimer + interval: animationDuration + onTriggered: { + if (!isNextChainDialogSet) { + dialogsChainFinished() + reject() + } + else { + reject() + } + } + } + + contentItem: FocusScope { + id: contentItemScope + anchors.fill: parent + anchors.margins: 0 + focus: true +// Layout.alignment: Qt.AlignTop +// Layout.fillHeight: true +// Layout.margins: 0 + + Keys.onPressed: { + event.accepted = true + if (event.modifiers === Qt.ControlModifier) + switch (event.key) { + case Qt.Key_A: + // detailedText.selectAll() + break + case Qt.Key_C: + // detailedText.copy() + break + case Qt.Key_Period: + if (Qt.platform.os === "osx") { + if (rejectable) rejectAnimated() + if (abortConfirmation) JsHelper.openAbortBox(root, abortBoxType) + } + break + } else switch (event.key) { + case Qt.Key_Escape: + case Qt.Key_Back: + if (rejectable) rejectAnimated() + if (abortConfirmation) JsHelper.openAbortBox(root, abortBoxType) + break + case Qt.Key_Enter: + case Qt.Key_Return: + if (acceptable) acceptAnimated() + else enterPressed() + break + } + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 0 + spacing: 0 + Layout.alignment: Qt.AlignTop + Layout.margins: 0 + //Layout.fillHeight: true + clip: true + + ColumnLayout { + id: customHeaderContainer + Layout.alignment: Qt.AlignTop + Layout.margins: 0 + spacing: 0 + clip: true + } + ColumnLayout { + id: customContentContainer + //Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + spacing: 0 + Layout.margins: 0 + clip: true + } + ColumnLayout { + id: customFooterContainer + Layout.alignment: Qt.AlignBottom + spacing: 0 + Layout.margins: 0 + clip: true + } + } + + FirstControl.BusyIndicator { + id: busyIndicator + anchors.centerIn: parent + running: false + + height: 50 + width: 50 + } + } + + Behavior on contentWidth { + NumberAnimation { duration: 20 } + } + Behavior on contentHeight { + NumberAnimation { duration: 20 } + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomDialogWindow.qml b/GUI/QtQuick/qml/StyledControls/CustomDialogWindow.qml new file mode 100644 index 000000000..e476852ae --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomDialogWindow.qml @@ -0,0 +1,31 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +// styled dialog popup +Dialog { + x: (width > parent.width) ? 0 : (parent.width - width) / 2 + y: (height > parent.height) ? 0 : (parent.height - height) / 2 + + focus: true + modal: true + closePolicy: Popup.NoAutoClose + spacing: 0 + margins: 0 + + background: Rectangle { + color: BSStyle.backgroundColor + border.color: BSStyle.dialogHeaderColor + border.pixelAligned: true + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomHeader.qml b/GUI/QtQuick/qml/StyledControls/CustomHeader.qml new file mode 100644 index 000000000..ea72d1bbc --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomHeader.qml @@ -0,0 +1,37 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +Button { + leftPadding: 0 + focusPolicy: Qt.NoFocus + + property color textColor: BSStyle.textColor + background: Rectangle { + color: "transparent" + } + + contentItem: Text { + text: parent.text + font.capitalization: Font.AllUppercase + color: { parent.enabled ? textColor : BSStyle.disabledHeaderColor } + font.pixelSize: 11 + } + + Rectangle { + height: 1 + width: parent.width + color: Qt.rgba(1, 1, 1, 0.1) + anchors.bottom: parent.bottom + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomHeaderPanel.qml b/GUI/QtQuick/qml/StyledControls/CustomHeaderPanel.qml new file mode 100644 index 000000000..8dab2e4bd --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomHeaderPanel.qml @@ -0,0 +1,42 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +Rectangle { +// property bool qmlTitleVisible: true //!mainWindow.isLiteMode + property string text + clip: true + color: "transparent" + height: 40 + + Rectangle { +// visible: qmlTitleVisible +// height: qmlTitleVisible ? 40 : 0 + anchors.fill: parent + color: BSStyle.dialogHeaderColor + } + + Text { +// visible: qmlTitleVisible +// height: qmlTitleVisible ? 40 : 0 + anchors.fill: parent + leftPadding: 10 + rightPadding: 10 + + text: parent.text + font.capitalization: Font.AllUppercase + color: BSStyle.textColor + font.pixelSize: 11 + verticalAlignment: Text.AlignVCenter + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomLabel.qml b/GUI/QtQuick/qml/StyledControls/CustomLabel.qml new file mode 100644 index 000000000..088570855 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomLabel.qml @@ -0,0 +1,23 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +Label { + horizontalAlignment: Text.AlignHLeft + font.pixelSize: 11 + color: { enabled ? BSStyle.labelsTextColor : BSStyle.disabledColor } + wrapMode: Text.WordWrap + topPadding: 5 + bottomPadding: 5 +} + diff --git a/GUI/QtQuick/qml/StyledControls/CustomLabelCopyableValue.qml b/GUI/QtQuick/qml/StyledControls/CustomLabelCopyableValue.qml new file mode 100644 index 000000000..97a33f77c --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomLabelCopyableValue.qml @@ -0,0 +1,58 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 + +import com.blocksettle.QmlFactory 1.0 + +import "../BsStyles" + +Label { + id: root + + property alias mouseArea: mouseArea + property string textForCopy + + font.pixelSize: 11 + color: "white" + wrapMode: Text.WordWrap + padding: 5 + onLinkActivated: Qt.openUrlExternally(link) + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + + acceptedButtons: Qt.RightButton + onClicked: { + if (mouse.button === Qt.RightButton) { + contextMenu.popup() + } + } + onPressAndHold: { + if (mouse.source === Qt.MouseEventNotSynthesized) { + contextMenu.popup() + } + } + + Menu { + id: contextMenu + MenuItem { + text: qsTr("Copy") + onTriggered: { + qmlFactory.setClipboard(root.textForCopy.length > 0 ? root.textForCopy : root.text) + } + } + } + } +} + diff --git a/GUI/QtQuick/qml/StyledControls/CustomLabelValue.qml b/GUI/QtQuick/qml/StyledControls/CustomLabelValue.qml new file mode 100644 index 000000000..3b021ddaf --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomLabelValue.qml @@ -0,0 +1,22 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +Label { + font.pixelSize: 11 + color: "white" + wrapMode: Text.WordWrap + topPadding: 5 + bottomPadding: 5 + onLinkActivated: Qt.openUrlExternally(link) +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomPasswordTextInput.qml b/GUI/QtQuick/qml/StyledControls/CustomPasswordTextInput.qml new file mode 100644 index 000000000..ef0f05b1e --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomPasswordTextInput.qml @@ -0,0 +1,50 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + + +TextField { + property bool allowShowPass: true + + horizontalAlignment: Text.AlignHLeft + font.pixelSize: 11 + color: BSStyle.inputsFontColor + padding: 0 + echoMode: button.pressed ? TextInput.Normal : TextInput.Password + selectByMouse: false + + background: Rectangle { + implicitWidth: 200 + implicitHeight: 25 + color:"transparent" + border.color: BSStyle.inputsBorderColor + + Button { + id: button + visible: allowShowPass + contentItem: Rectangle { + color: "transparent" + Image { + fillMode: Image.PreserveAspectFit + anchors.fill: parent + source: "qrc:/resources/eye.png" + } + } + padding: 2 - (button.pressed ? 1 : 0) + background: Rectangle {color: "transparent"} + anchors.right: parent.right + width: 23 + height: 23 + } + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomProgressBar.qml b/GUI/QtQuick/qml/StyledControls/CustomProgressBar.qml new file mode 100644 index 000000000..08387d148 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomProgressBar.qml @@ -0,0 +1,40 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +ProgressBar { + id: control + value: 0.5 + topPadding: 1 + bottomPadding: 1 + + background: Rectangle { + implicitWidth: 200 + implicitHeight: 6 + color: BSStyle.progressBarBgColor + radius: 3 + } + + contentItem: Item { + implicitWidth: 200 + implicitHeight: 4 + + Rectangle { + width: control.visualPosition * parent.width + height: parent.height + radius: 2 + color: BSStyle.progressBarColor + } + } +} + diff --git a/GUI/QtQuick/qml/StyledControls/CustomRadioButton.qml b/GUI/QtQuick/qml/StyledControls/CustomRadioButton.qml new file mode 100644 index 000000000..87f4abaa4 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomRadioButton.qml @@ -0,0 +1,48 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +RadioButton { + id: control + text: parent.text + focusPolicy: Qt.NoFocus + + indicator: Rectangle { + implicitWidth: 16 + implicitHeight: 16 + x: control.leftPadding + y: parent.height / 2 - height / 2 + radius: 11 + border.color: control.checked ? BSStyle.buttonsBorderColor : BSStyle.buttonsUncheckedColor + color: "transparent" + + Rectangle { + width: 8 + height: 8 + x: 4 + y: 4 + radius: 7 + color: control.checked ? BSStyle.buttonsPrimaryMainColor : BSStyle.buttonsUncheckedColor + visible: control.checked + } + } + + contentItem: Text { + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.checked ? BSStyle.textColor : BSStyle.buttonsUncheckedColor + verticalAlignment: Text.AlignVCenter + leftPadding: control.indicator.width + control.spacing + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomSwitch.qml b/GUI/QtQuick/qml/StyledControls/CustomSwitch.qml new file mode 100644 index 000000000..df19894c7 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomSwitch.qml @@ -0,0 +1,130 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +Switch { + id: control + text: parent.text + checked: true + + contentItem: Text { + rightPadding: control.indicator.width + control.spacing + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.checked ? BSStyle.switchCheckedColor : (signerStatus.socketOk ? BSStyle.switchUncheckedColor : BSStyle.switchOrangeColor) + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + indicator: Rectangle { + id: border_ + implicitWidth: 40 + implicitHeight: 20 + x: control.width - width - control.rightPadding + y: parent.height / 2 - height / 2 + radius: 10 + color: { + if (control.enabled) { + if (control.checked) { + return BSStyle.switchCheckedColor + } + else { + return BSStyle.switchBgColor + } + } + else { + return BSStyle.switchDisabledBgColor + + } + } + border.color: { + if (control.enabled) { + if (control.checked) { + return BSStyle.switchCheckedColor + } + else { + return BSStyle.switchUncheckedColor + } + } + else { + return BSStyle.switchDisabledBgColor + } + } + Rectangle { + id: circle_ + x: control.checked ? parent.width - width : 0 + width: 20 + height: 20 + radius: 10 + + color: { + if (control.enabled) { + if (control.checked) { + return BSStyle.textColor + } + else { + return BSStyle.switchUncheckedColor + } + } + else { + return "#71787f" + + } + } + + border.color: { + if (control.enabled) { + if (control.checked) { + return BSStyle.switchCheckedColor + } + else { + return BSStyle.backgroundColor + } + } + else { + return BSStyle.switchDisabledBgColor + } + } + } + } + + background: Rectangle { + implicitWidth: 80 + implicitHeight: 20 + visible: control.down + color: BSStyle.switchBgColor + } + +// states: [ +// State { +// name: "checked" +// when: control.checked +// }, +// State { +// name: "uncheked" +// when: !control.checked +// } +// ] + +// transitions: [ +// Transition { +// to: "checked" +// NumberAnimation { +// target: circle_ +// properties: "color" +// duration: 300 +// } +// } +// ] +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomTabButton.qml b/GUI/QtQuick/qml/StyledControls/CustomTabButton.qml new file mode 100644 index 000000000..0abd48a18 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomTabButton.qml @@ -0,0 +1,46 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +TabButton { + id: control + text: parent.text + property alias cText: text_ + focusPolicy: Qt.NoFocus + + contentItem: Text { + id: text_ + text: control.text + font.capitalization: Font.AllUppercase + font.pointSize: 10 + color: control.checked ? (control.down ? BSStyle.textPressedColor : BSStyle.textColor) : BSStyle.buttonsUncheckedColor + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + background: Rectangle { + implicitWidth: 100 + implicitHeight: 50 + opacity: enabled ? 1 : 0.3 + color: control.checked ? (control.down ? BSStyle.backgroundPressedColor : BSStyle.backgroundColor) : "0f1f24" + + Rectangle { + width: parent.width + height: 2 + color: control.checked ? (control.down ? BSStyle.textPressedColor : BSStyle.buttonsPrimaryMainColor) : "transparent" + anchors.top: parent.top + } + } +} + diff --git a/GUI/QtQuick/qml/StyledControls/CustomTextArea.qml b/GUI/QtQuick/qml/StyledControls/CustomTextArea.qml new file mode 100644 index 000000000..0654d4621 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomTextArea.qml @@ -0,0 +1,32 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import "../BsStyles" + +TextArea { + id: root + horizontalAlignment: Text.AlignHLeft + font.pixelSize: 11 + color: "white" + wrapMode: TextEdit.WordWrap + + background: Rectangle { + implicitWidth: 200 + implicitHeight: 50 + color:"transparent" + border.color: BSStyle.inputsBorderColor + } + + CustomContextMenu { + anchors.fill: parent + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomTextInput.qml b/GUI/QtQuick/qml/StyledControls/CustomTextInput.qml new file mode 100644 index 000000000..644c77d7c --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomTextInput.qml @@ -0,0 +1,34 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 + +import "../BsStyles" + +TextField { + id: root + horizontalAlignment: Text.AlignHLeft + font.pixelSize: 11 + color: BSStyle.inputsFontColor + padding: 0 + selectByMouse: true + + background: Rectangle { + implicitWidth: 200 + implicitHeight: 25 + color: "transparent" + border.color: BSStyle.inputsBorderColor + } + + CustomContextMenu { + anchors.fill: parent + } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomTitleDialogWindow.qml b/GUI/QtQuick/qml/StyledControls/CustomTitleDialogWindow.qml new file mode 100644 index 000000000..b8918134a --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomTitleDialogWindow.qml @@ -0,0 +1,47 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.0 + +import "../BsStyles" + +// dialog window with header +CustomDialog { + id: root + // property bool qmlTitleVisible: true //: !mainWindow.isLiteMode + property alias headerPanel: headerPanel + property bool fixedHeight: false + property var customHeader: ColumnLayout { + id: layout + spacing: 0 + Layout.alignment: Qt.AlignTop + Layout.margins: 0 + + CustomHeaderPanel { + id: headerPanel + Layout.fillWidth: true + //qmlTitleVisible: root.qmlTitleVisible + text: root.title + } + } + + height: fixedHeight ? undefined: cHeaderHeight + cContentHeight + cFooterHeight + + function isApplicationWindow(item) { + return item instanceof ApplicationWindow + } + + cHeaderItem: customHeader +// onNextChainDialogChangedOverloaded: { +// nextDialog.qmlTitleVisible = qmlTitleVisible +// } +} diff --git a/GUI/QtQuick/qml/StyledControls/CustomTitleDialogWindowWithExpander.qml b/GUI/QtQuick/qml/StyledControls/CustomTitleDialogWindowWithExpander.qml new file mode 100644 index 000000000..dc09e8ce5 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/CustomTitleDialogWindowWithExpander.qml @@ -0,0 +1,89 @@ +/* + +*********************************************************************************** +* Copyright (C) 2018 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.0 + +import "../BsStyles" +import "../BsControls" + +BSWalletHandlerDialog { + id: root + + property string headerButtonText: "" + signal headerButtonClicked() + + customHeader: ColumnLayout { + id: layout + spacing: 0 + Layout.alignment: Qt.AlignTop + Layout.margins: 0 + + Rectangle { + id: rect + property string text : root.title + clip: true + color: "transparent" + height: 40 + + Layout.fillWidth: true + + Rectangle { + anchors.fill: rect + color: BSStyle.dialogHeaderColor + } + + Text { + anchors.fill: rect + leftPadding: 10 + rightPadding: 10 + + text: rect.text + font.capitalization: Font.AllUppercase + color: BSStyle.textColor + font.pixelSize: 11 + verticalAlignment: Text.AlignVCenter + } + + Button { + id: btnExpand + width: 100 + anchors.right: rect.right + anchors.top: rect.top + anchors.bottom: rect.bottom + + contentItem: Text { + text: headerButtonText + color: BSStyle.textColor + font.pixelSize: 11 + font.underline: true + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + elide: Text.ElideNone + } + + background: Rectangle { + implicitWidth: 70 + implicitHeight: 35 + color: "transparent" + } + + MouseArea { + anchors.fill: parent + enabled: false + cursorShape: Qt.PointingHandCursor + } + + onClicked: headerButtonClicked() + } + } + } +} diff --git a/GUI/QtQuick/qml/StyledControls/qmldir b/GUI/QtQuick/qml/StyledControls/qmldir new file mode 100644 index 000000000..c5f3cae76 --- /dev/null +++ b/GUI/QtQuick/qml/StyledControls/qmldir @@ -0,0 +1,23 @@ +module CustomControls + +CustomButton 1.0 CustomButton.qml +CustomButtonBar 1.0 CustomButtonBar.qml +CustomCheckBox 1.0 CustomCheckBox.qml +CustomComboBox 1.0 CustomComboBox.qml +CustomContainer 1.0 CustomContainer.qml +CustomDialogWindow 1.0 CustomDialogWindow.qml +CustomDialog 1.0 CustomDialog.qml +CustomHeader 1.0 CustomHeader.qml +CustomHeaderPanel 1.0 CustomHeaderPanel.qml +CustomLabel 1.0 CustomLabel.qml +CustomLabelValue 1.0 CustomLabelValue.qml +CustomLabelCopyableValue 1.0 CustomLabelCopyableValue.qml +CustomProgressBar 1.0 CustomProgressBar.qml +CustomRadioButton 1.0 CustomRadioButton.qml +CustomSwitch 1.0 CustomSwitch.qml +CustomTabButton 1.0 CustomTabButton.qml +CustomTextArea 1.0 CustomTextArea.qml +CustomTextInput 1.0 CustomTextInput.qml +CustomPasswordTextInput 1.0 CustomPasswordTextInput.qml +CustomTitleDialogWindow 1.0 CustomTitleDialogWindow.qml +CustomTitleDialogWindowWithExpander 1.0 CustomTitleDialogWindowWithExpander.qml diff --git a/GUI/QtQuick/qml/main.qml b/GUI/QtQuick/qml/main.qml new file mode 100644 index 000000000..0d9e070aa --- /dev/null +++ b/GUI/QtQuick/qml/main.qml @@ -0,0 +1,135 @@ +/* + +*********************************************************************************** +* Copyright (C) 2022, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import QtQuick 2 +import QtQuick.Controls 2 +import QtQuick.Layouts 1.3 +import QtQuick.Window 2 +import "StyledControls" 1 +import "BsStyles" 1 +//import Qt.labs.settings 1.0 + +/* +import "BsControls" +import "BsDialogs" +import "js/helper.js" as JsHelper +*/ + +ApplicationWindow { + id: mainWindow + width: 800 + height: 600 + + visible: false + title: qsTr("BlockSettle Terminal") + + property var currentDialog: ({}) + readonly property int resizeAnimationDuration: 25 + + Component.onCompleted: { + mainWindow.flags = Qt.CustomizeWindowHint | Qt.MSWindowsFixedSizeDialogHint | + Qt.Dialog | Qt.WindowSystemMenuHint | + Qt.WindowTitleHint | Qt.WindowCloseButtonHint + hide() +// qmlFactory.installEventFilterToObj(mainWindow) +// qmlFactory.applyWindowFix(mainWindow) + } + + color: BSStyle.backgroundColor + + overlay.modal: Rectangle { + color: BSStyle.backgroundModalColor + } + overlay.modeless: Rectangle { + color: BSStyle.backgroundModeLessColor + } + + // attached to use from c++ + function messageBoxCritical(title, text, details) { + return JsHelper.messageBoxCritical(title, text, details) + } + + InfoBanner { + id: ibSuccess + bgColor: "darkgreen" + } + InfoBanner { + id: ibFailure + bgColor: "darkred" + } + +/* function raiseWindow() { + JsHelper.raiseWindow(mainWindow) + } + function hideWindow() { + JsHelper.hideWindow(mainWindow) + } + + function customDialogRequest(dialogName, data) { + var newDialog = JsHelper.customDialogRequest(dialogName, data) + if (newDialog) { + raiseWindow() + JsHelper.prepareDialog(newDialog) + } + } + + function invokeQmlMethod(method, cppCallback, argList) { + JsHelper.evalWorker(method, cppCallback, argList) + }*/ + + function moveMainWindowToScreenCenter() { + mainWindow.x = Screen.virtualX + (Screen.width - mainWindow.width) / 2 + mainWindow.y = Screen.virtualY + (Screen.height - mainWindow.height) / 2 + } + + function resizeAnimated(w,h) { + mwWidthAnimation.from = mainWindow.width + mwWidthAnimation.to = w + mwWidthAnimation.restart() + + mwHeightAnimation.from = mainWindow.height + mwHeightAnimation.to = h + mwHeightAnimation.restart() + + mwXAnimation.from = mainWindow.x + mwXAnimation.to = Screen.virtualX + (Screen.width - w) / 2 + mwXAnimation.restart() + + mwYAnimation.from = mainWindow.y + mwYAnimation.to = Screen.virtualY + (Screen.height - h) / 2 + mwYAnimation.restart() + } + + NumberAnimation { + id: mwWidthAnimation + target: mainWindow + property: "width" + duration: resizeAnimationDuration + } + NumberAnimation { + id: mwHeightAnimation + target: mainWindow + property: "height" + duration: resizeAnimationDuration + } + + NumberAnimation { + id: mwXAnimation + target: mainWindow + property: "x" + duration: resizeAnimationDuration + } + NumberAnimation { + id: mwYAnimation + target: mainWindow + property: "y" + duration: resizeAnimationDuration + } +} diff --git a/GUI/QtQuick/qtquick.qrc b/GUI/QtQuick/qtquick.qrc new file mode 100644 index 000000000..22130886b --- /dev/null +++ b/GUI/QtQuick/qtquick.qrc @@ -0,0 +1,29 @@ + + + images/full_logo.png + images/bs_logo.png + qml/main.qml + qml/InfoBanner.qml + qml/InfoBannerComponent.qml + qml/InfoBar.qml + qml/StyledControls/CustomButton.qml + qml/StyledControls/CustomButtonBar.qml + qml/StyledControls/CustomCheckBox.qml + qml/StyledControls/CustomComboBox.qml + qml/StyledControls/CustomContainer.qml + qml/StyledControls/CustomDialog.qml + qml/StyledControls/CustomHeader.qml + qml/StyledControls/CustomHeaderPanel.qml + qml/StyledControls/CustomLabel.qml + qml/StyledControls/CustomLabelValue.qml + qml/StyledControls/CustomProgressBar.qml + qml/StyledControls/CustomRadioButton.qml + qml/StyledControls/CustomSwitch.qml + qml/StyledControls/CustomTabButton.qml + qml/StyledControls/CustomTextArea.qml + qml/StyledControls/CustomTextInput.qml + qml/StyledControls/qmldir + qml/BsStyles/BSStyle.qml + qml/BsStyles/qmldir + + diff --git a/GUI/QtWidgets/CMakeLists.txt b/GUI/QtWidgets/CMakeLists.txt new file mode 100644 index 000000000..0b8eb9ba0 --- /dev/null +++ b/GUI/QtWidgets/CMakeLists.txt @@ -0,0 +1,83 @@ +# +# +# *********************************************************************************** +# * Copyright (C) 2020 - 2021, BlockSettle AB +# * Distributed under the GNU Affero General Public License (AGPL v3) +# * See LICENSE or http://www.gnu.org/licenses/agpl.html +# * +# ********************************************************************************** +# +# + +CMAKE_MINIMUM_REQUIRED(VERSION 3.3) + +PROJECT(${TERMINAL_GUI_QT_NAME}) + +FILE(GLOB SOURCES + *.cpp +) +FILE(GLOB HEADERS + *.h +) + +FILE(GLOB UI_FILES + ${BLOCKSETTLE_UI_INCLUDE_DIR}/BSTerminalMainWindow.ui +# ${BLOCKSETTLE_UI_INCLUDE_DIR}/CustomControls/*.ui + ${BLOCKSETTLE_UI_INCLUDE_DIR}/InfoDialogs/*.ui +# ${BLOCKSETTLE_UI_INCLUDE_DIR}/ManageEncryption/*.ui +# ${BLOCKSETTLE_UI_INCLUDE_DIR}/Settings/*.ui +# ${BLOCKSETTLE_UI_INCLUDE_DIR}/Trading/*.ui + ${BLOCKSETTLE_UI_INCLUDE_DIR}/ChatUI/*.ui +# ${BLOCKSETTLE_UI_INCLUDE_DIR}/ChatUI/OTCShieldWidgets/*.ui +) + +INCLUDE_DIRECTORIES(../../BlockSettleUILib) +INCLUDE_DIRECTORIES(BlockSettleUILib) +INCLUDE_DIRECTORIES(${COMMON_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${BOTAN_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${WALLET_LIB_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${COMMON_UI_LIB_INCLUDE_DIR} ) +INCLUDE_DIRECTORIES(${BS_COMMON_ENUMS_INCLUDE_DIR} ) +INCLUDE_DIRECTORIES(${BS_COMMUNICATION_INCLUDE_DIR} ) +INCLUDE_DIRECTORIES(${BLOCKSETTLE_UI_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${Qt5Svg_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${Qt5Gui_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Widgets_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Core_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${Qt5Network_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Qml_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5DBus_INCLUDE_DIRS} ) +INCLUDE_DIRECTORIES(${Qt5Charts_INCLUDE_DIRS} ) + +qt5_wrap_ui(GENERATED_UI ${UI_FILES}) + +ADD_LIBRARY(${TERMINAL_GUI_QT_NAME} + ${SOURCES} + ${HEADERS} + ${GENERATED_UI} +) + +TARGET_INCLUDE_DIRECTORIES(${TERMINAL_GUI_QT_NAME} + PUBLIC ${BLOCK_SETTLE_ROOT}/GUI/QtWidgets + PRIVATE ${BLOCK_SETTLE_ROOT}/Core + PRIVATE ${BLOCK_SETTLE_ROOT}/common/BlocksettleNetworkingLib + PRIVATE ${BLOCK_SETTLE_ROOT}/BlockSettleUILib +) + +TARGET_LINK_LIBRARIES(${TERMINAL_GUI_QT_NAME} + ${BLOCKSETTLE_UI_LIBRARY_NAME} + ${BS_NETWORK_LIB_NAME} + ${CRYPTO_LIB_NAME} + Qt5::Network + Qt5::Core + Qt5::Widgets + Qt5::Gui + Qt5::Network + Qt5::PrintSupport + Qt5::Core + Qt5::Svg + Qt5::DBus + ${QT_LIBS} + ${OS_SPECIFIC_LIBS} + ${OPENSSL_LIBS} +) diff --git a/GUI/QtWidgets/MainWindow.cpp b/GUI/QtWidgets/MainWindow.cpp new file mode 100644 index 000000000..873c93300 --- /dev/null +++ b/GUI/QtWidgets/MainWindow.cpp @@ -0,0 +1,1108 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "MainWindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ui_BSTerminalMainWindow.h" +#include "ApiAdapter.h" +#include "ApplicationSettings.h" +#include "BSMessageBox.h" +#include "CreateTransactionDialogAdvanced.h" +#include "CreateTransactionDialogSimple.h" +#include "DialogManager.h" +#include "InfoDialogs/AboutDialog.h" +#include "InfoDialogs/StartupDialog.h" +#include "InfoDialogs/SupportDialog.h" +#include "ImportWalletDialog.h" +#include "LoginWindow.h" +#include "NotificationCenter.h" +#include "Settings/ConfigDialog.h" +#include "StatusBarView.h" +#include "TabWithShortcut.h" +#include "TerminalMessage.h" +#include "TransactionsViewModel.h" +#include "UiUtils.h" + +#include "terminal.pb.h" + +using namespace bs::gui::qt; +using namespace BlockSettle::Terminal; + +MainWindow::MainWindow(const std::shared_ptr &logger + , const std::shared_ptr &queue + , const std::shared_ptr &user) + : QMainWindow(nullptr) + , ui_(new Ui::BSTerminalMainWindow()), logger_(logger) + , queue_(queue), guiUser_(user) + , settingsUser_(std::make_shared(bs::message::TerminalUsers::Settings)) +{ + UiUtils::SetupLocale(); + + ui_->setupUi(this); + + setupShortcuts(); + setupInfoWidget(); + + loginButtonText_ = tr("Login"); + + connect(ui_->actionQuit, &QAction::triggered, qApp, &QCoreApplication::quit); + + setupIcon(); + UiUtils::setupIconFont(this); + notifCenter_ = std::make_shared(logger_, ui_.get(), sysTrayIcon_, this); + + statusBarView_ = std::make_shared(ui_->statusbar); + + setupToolbar(); + setupMenu(); + + initChartsView(); + + updateAppearance(); + setWidgetsAuthorized(false); + + initWidgets(); + + ui_->widgetTransactions->setEnabled(false); + actSend_->setEnabled(false); +} + +void MainWindow::setWidgetsAuthorized(bool authorized) +{ + // Update authorized state for some widgets + //ui_->widgetPortfolio->setAuthorized(authorized); +} + +void MainWindow::onGetGeometry(const QRect &mainGeom) +{ + if (mainGeom.isEmpty()) { + return; + } + auto geom = mainGeom; + setGeometry(geom); // This call is required for screenNumber() method to work properly + +#ifdef Q_OS_WINDOWS + int screenNo = QApplication::desktop()->screenNumber(this); + if (screenNo < 0) { + screenNo = 0; + } + const auto screenGeom = QApplication::desktop()->screenGeometry(screenNo); + if (!screenGeom.contains(geom)) { + const int screenWidth = screenGeom.width() * 0.9; + const int screenHeight = screenGeom.height() * 0.9; + geom.setWidth(std::min(geom.width(), screenWidth)); + geom.setHeight(std::min(geom.height(), screenHeight)); + geom.moveCenter(screenGeom.center()); + } + /* + const auto screen = qApp->screens()[screenNo]; + const float pixelRatio = screen->devicePixelRatio(); + if (pixelRatio > 1.0) { + //FIXME: re-check on hi-res screen + }*/ +#else + if (QApplication::desktop()->screenNumber(this) == -1) { + auto currentScreenRect = QApplication::desktop()->screenGeometry(QCursor::pos()); + // Do not delete 0.9 multiplier, since in some system window size is applying without system native toolbar + geom.setWidth(std::min(geom.width(), static_cast(currentScreenRect.width() * 0.9))); + geom.setHeight(std::min(geom.height(), static_cast(currentScreenRect.height() * 0.9))); + geom.moveCenter(currentScreenRect.center()); + } +#endif // not Windows + QTimer::singleShot(10, [this, geom] { + setGeometry(geom); + }); +} + +void MainWindow::onSetting(int setting, const QVariant &value) +{ + switch (static_cast(setting)) { + case ApplicationSettings::GUI_main_tab: + ui_->tabWidget->setCurrentIndex(value.toInt()); + break; + case ApplicationSettings::ShowInfoWidget: + ui_->infoWidget->setVisible(value.toBool()); + break; + case ApplicationSettings::AdvancedTxDialogByDefault: + advTxDlgByDefault_ = value.toBool(); + break; + case ApplicationSettings::closeToTray: + closeToTray_ = value.toBool(); + updateAppearance(); + break; + case ApplicationSettings::envConfiguration: { + const auto& newEnvCfg = static_cast(value.toInt()); + if (envConfig_ != newEnvCfg) { + envConfig_ = newEnvCfg; + //TODO: maybe initiate relog and Celer/proxy reconnect + } + ui_->widgetPortfolio->onEnvConfig(value.toInt()); + } + break; + case ApplicationSettings::rememberLoginUserName: + case ApplicationSettings::celerUsername: + break; + default: break; + } + + if (cfgDlg_) { + cfgDlg_->onSetting(setting, value); + } +} + +void bs::gui::qt::MainWindow::onSettingsState(const ApplicationSettings::State& state) +{ + if (cfgDlg_) { + cfgDlg_->onSettingsState(state); + } +} + +void MainWindow::onArmoryStateChanged(int state, unsigned int blockNum) +{ + topBlock_ = blockNum; + statusBarView_->onBlockchainStateChanged(state, blockNum); + ui_->widgetExplorer->onNewBlock(blockNum); +} + +void MainWindow::onNewBlock(int state, unsigned int blockNum) +{ + topBlock_ = blockNum; + statusBarView_->onBlockchainStateChanged(state, blockNum); + + if (txModel_) { + txModel_->onNewBlock(blockNum); + } + ui_->widgetWallets->onNewBlock(blockNum); + ui_->widgetExplorer->onNewBlock(blockNum); +} + +void MainWindow::onWalletsReady() +{ + logger_->debug("[{}]", __func__); + ui_->widgetTransactions->setEnabled(true); + actSend_->setEnabled(true); + emit needLedgerEntries({}); +} + +void MainWindow::onSignerStateChanged(int state, const std::string &details) +{ + statusBarView_->onSignerStatusChanged(static_cast(state) + , QString::fromStdString(details)); +} + +void MainWindow::onHDWallet(const bs::sync::WalletInfo &wi) +{ + ui_->widgetWallets->onHDWallet(wi); + ui_->widgetPortfolio->onHDWallet(wi); +} + +void bs::gui::qt::MainWindow::onWalletDeleted(const bs::sync::WalletInfo& wi) +{ + ui_->widgetWallets->onWalletDeleted(wi); + if (txDlg_) { + txDlg_->onWalletDeleted(wi); + } + ui_->widgetTransactions->onWalletDeleted(wi); +} + +void MainWindow::onHDWalletDetails(const bs::sync::HDWalletData &hdWallet) +{ + ui_->widgetWallets->onHDWalletDetails(hdWallet); + ui_->widgetPortfolio->onHDWalletDetails(hdWallet); + ui_->widgetTransactions->onHDWalletDetails(hdWallet); +} + +void MainWindow::onWalletsList(const std::string &id, const std::vector& wallets) +{ + if (txDlg_) { + txDlg_->onWalletsList(id, wallets); + } +} + +void bs::gui::qt::MainWindow::onWalletData(const std::string& walletId + , const bs::sync::WalletData& wd) +{ + //ui_->widgetRFQ->onWalletData(walletId, wd); +} + +void MainWindow::onAddresses(const std::string& walletId + , const std::vector &addrs) +{ + if (txDlg_) { + txDlg_->onAddresses(walletId, addrs); + } + else { + ui_->widgetWallets->onAddresses(walletId, addrs); + } +} + +void MainWindow::onAddressComments(const std::string &walletId + , const std::map &comments) +{ + if (txDlg_) { + txDlg_->onAddressComments(walletId, comments); + } + else { + ui_->widgetWallets->onAddressComments(walletId, comments); + } +} + +void MainWindow::onWalletBalance(const bs::sync::WalletBalanceData &wbd) +{ + if (txDlg_) { + txDlg_->onAddressBalances(wbd.id, wbd.addrBalances); + } + ui_->widgetWallets->onWalletBalance(wbd); + ui_->widgetPortfolio->onWalletBalance(wbd); + statusBarView_->onXbtBalance(wbd); +} + +void MainWindow::onLedgerEntries(const std::string &filter, uint32_t totalPages + , uint32_t curPage, uint32_t curBlock, const std::vector &entries) +{ + if (filter.empty()) { + txModel_->onLedgerEntries(filter, totalPages, curPage, curBlock, entries); + } + else { + ui_->widgetWallets->onLedgerEntries(filter, totalPages, curPage, curBlock, entries); + } +} + +void MainWindow::onTXDetails(const std::vector &txDet) +{ + txModel_->onTXDetails(txDet); + ui_->widgetWallets->onTXDetails(txDet); + ui_->widgetExplorer->onTXDetails(txDet); +} + +void bs::gui::qt::MainWindow::onNewZCs(const std::vector& txDet) +{ + QStringList lines; + for (const auto& tx : txDet) { + lines << tr("TX: %1 %2 %3").arg(tr(bs::sync::Transaction::toString(tx.direction))) + .arg(QString::fromStdString(tx.amount)) + .arg(QString::fromStdString(tx.walletSymbol)); + if (!tx.walletName.empty()) { + lines << tr("Wallet: %1").arg(QString::fromStdString(tx.walletName)); + } + + QString mainAddress, multipleAddresses = tr("%1 output addresses"); + switch (tx.outAddresses.size()) { + case 0: + switch (tx.outputAddresses.size()) { + case 1: + mainAddress = QString::fromStdString(tx.outputAddresses.at(0).address.display()); + break; + default: + mainAddress = multipleAddresses.arg(tx.outputAddresses.size()); + break; + } + break; + case 1: + mainAddress = QString::fromStdString(tx.outAddresses.at(0).display()); + break; + default: + mainAddress = multipleAddresses.arg(tx.outAddresses.size()); + break; + } + lines << mainAddress << QString(); + } + const auto& title = tr("New blockchain transaction"); + notifCenter_->enqueue(bs::ui::NotifyType::BlockchainTX, { title, lines.join(tr("\n")) }); +} + +void bs::gui::qt::MainWindow::onZCsInvalidated(const std::vector& txHashes) +{ + if (txModel_) { + txModel_->onZCsInvalidated(txHashes); + } +} + +void MainWindow::onAddressHistory(const bs::Address& addr, uint32_t curBlock, const std::vector& entries) +{ + ui_->widgetExplorer->onAddressHistory(addr, curBlock, entries); +} + +void bs::gui::qt::MainWindow::onChangeAddress(const std::string& walletId + , const bs::Address& addr) +{ + logger_->debug("[{}] {} {}", __func__, walletId, addr.display()); + if (txDlg_) { + txDlg_->onChangeAddress(walletId, addr); + } +} + +void MainWindow::onFeeLevels(const std::map& feeLevels) +{ + if (txDlg_) { + txDlg_->onFeeLevels(feeLevels); + } +} + +void bs::gui::qt::MainWindow::onUTXOs(const std::string& id + , const std::string& walletId, const std::vector& utxos) +{ + if (txDlg_) { + txDlg_->onUTXOs(id, walletId, utxos); + } +} + +void bs::gui::qt::MainWindow::onSignedTX(const std::string& id, BinaryData signedTX + , bs::error::ErrorCode result) +{ + if (txDlg_) { + txDlg_->onSignedTX(id, signedTX, result); + } +} + +void bs::gui::qt::MainWindow::onArmoryServers(const QList& servers, int idxCur, int idxConn) +{ + if (cfgDlg_) { + cfgDlg_->onArmoryServers(servers, idxCur, idxConn); + } +} + +void bs::gui::qt::MainWindow::onSignerSettings(const QList& signers + , const std::string& ownKey, int idxCur) +{ + if (cfgDlg_) { + cfgDlg_->onSignerSettings(signers, ownKey, idxCur); + } +} + +void MainWindow::showStartupDialog(bool showLicense) +{ + StartupDialog startupDialog(showLicense, this); +// startupDialog.init(applicationSettings_); + int result = startupDialog.exec(); + + if (showLicense && (result == QDialog::Rejected)) { + hide(); + QTimer::singleShot(100, [] { qApp->exit(EXIT_FAILURE); }); + return; + } + + // Need update armory settings if case user selects TestNet + const auto &netType = startupDialog.getSelectedNetworkType(); + ApplicationSettings::EnvConfiguration envConfig = (netType == NetworkType::TestNet) ? + ApplicationSettings::EnvConfiguration::Test : ApplicationSettings::EnvConfiguration::Production; + + SettingsMessage msg; + auto msgArmory = msg.mutable_armory_server(); + msgArmory->set_network_type(static_cast(netType)); + msgArmory->set_server_name(ARMORY_BLOCKSETTLE_NAME); + + auto env = bs::message::Envelope::makeRequest(guiUser_, settingsUser_, msg.SerializeAsString()); + queue_->pushFill(env); + + msg.Clear(); + auto msgReq = msg.mutable_put_request(); + auto msgPut = msgReq->add_responses(); + auto req = msgPut->mutable_request(); + req->set_source(SettingSource_Local); + req->set_index(SetIdx_Environment); + req->set_type(SettingType_Int); + msgPut->set_i(static_cast(envConfig)); + + msgPut = msgReq->add_responses(); + req = msgPut->mutable_request(); + req->set_source(SettingSource_Local); + req->set_index(SetIdx_Initialized); + req->set_type(SettingType_Bool); + msgPut->set_b(true); + + env = bs::message::Envelope::makeRequest(guiUser_, settingsUser_, msg.SerializeAsString()); + queue_->pushFill(env); +} + +bool MainWindow::event(QEvent *event) +{ + if (event->type() == QEvent::WindowActivate) { + auto tabChangedSignal = QMetaMethod::fromSignal(&QTabWidget::currentChanged); + int currentIndex = ui_->tabWidget->currentIndex(); + tabChangedSignal.invoke(ui_->tabWidget, Q_ARG(int, currentIndex)); + } + return QMainWindow::event(event); +} + +MainWindow::~MainWindow() +{ + NotificationCenter::destroyInstance(); +} + +void MainWindow::setupToolbar() +{ + actSend_ = new QAction(tr("Send Bitcoin"), this); + connect(actSend_, &QAction::triggered, this, &MainWindow::onSend); + + actNewAddress_ = new QAction(tr("Generate &Address"), this); + connect(actNewAddress_, &QAction::triggered, this, &MainWindow::onGenerateAddress); + + actLogin_ = new QAction(tr("Login to BlockSettle"), this); + connect(actLogin_, &QAction::triggered, this, &MainWindow::onLoginInitiated); + + actLogout_ = new QAction(tr("Logout from BlockSettle"), this); + connect(actLogout_, &QAction::triggered, this, &MainWindow::onLogoutInitiated); + + setupTopRightWidget(); + + actLogout_->setVisible(false); + + connect(ui_->pushButtonUser, &QPushButton::clicked, this, &MainWindow::onButtonUserClicked); + + QMenu* trayMenu = new QMenu(this); + QAction* trayShowAction = trayMenu->addAction(tr("&Open Terminal")); + connect(trayShowAction, &QAction::triggered, this, &QMainWindow::show); + trayMenu->addSeparator(); + + trayMenu->addAction(actSend_); + trayMenu->addAction(actNewAddress_); + trayMenu->addAction(ui_->actionSettings); + + trayMenu->addSeparator(); + trayMenu->addAction(ui_->actionQuit); + sysTrayIcon_->setContextMenu(trayMenu); +} + +void MainWindow::setupTopRightWidget() +{ + auto toolBar = new QToolBar(this); + toolBar->setObjectName(QLatin1String("mainToolBar")); + toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + ui_->tabWidget->setCornerWidget(toolBar, Qt::TopRightCorner); + + toolBar->addAction(actSend_); + toolBar->addAction(actNewAddress_); + + for (int i = 0; i < toolBar->children().size(); ++i) { + auto *toolButton = qobject_cast(toolBar->children().at(i)); + if (toolButton && (toolButton->defaultAction() == actSend_ + || toolButton->defaultAction() == actNewAddress_)) { + toolButton->setObjectName(QLatin1String("mainToolBarActions")); + } + } + +#if defined(Q_OS_WIN) + ui_->tabWidget->setProperty("onWindows", QVariant(true)); +#elif defined(Q_OS_LINUX) + ui_->tabWidget->setProperty("onLinux", QVariant(true)); +#else + ui_->tabWidget->setProperty("onMacos", QVariant(true)); +#endif + + auto *prevStyle = ui_->tabWidget->style(); + ui_->tabWidget->setStyle(nullptr); + ui_->tabWidget->setStyle(prevStyle); +} + +void MainWindow::setupIcon() +{ + QIcon icon; + QString iconFormatString = QString::fromStdString(":/ICON_BS_%1"); + + for (const int s : {16, 24, 32}) { + icon.addFile(iconFormatString.arg(s), QSize(s, s)); + } + + setWindowIcon(icon); + + sysTrayIcon_ = std::make_shared(icon, this); + sysTrayIcon_->setToolTip(windowTitle()); + sysTrayIcon_->show(); + + connect(sysTrayIcon_.get(), &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) { + if (reason == QSystemTrayIcon::Context) { + // Right click, this is handled by the menu, so we don't do anything here. + return; + } + + setWindowState(windowState() & ~Qt::WindowMinimized); + show(); + raise(); + activateWindow(); + }); + + connect(qApp, &QCoreApplication::aboutToQuit, sysTrayIcon_.get(), &QSystemTrayIcon::hide); + connect(qApp, SIGNAL(lastWindowClosed()), sysTrayIcon_.get(), SLOT(hide())); +} + +void MainWindow::setupInfoWidget() +{ + connect(ui_->introductionBtn, &QPushButton::clicked, this, []() { + QDesktopServices::openUrl(QUrl(QLatin1String("https://www.youtube.com/watch?v=mUqKq9GKjmI"))); + }); + connect(ui_->tutorialsButton, &QPushButton::clicked, this, []() { + QDesktopServices::openUrl(QUrl(QLatin1String("https://blocksettle.com/tutorials"))); + }); + connect(ui_->closeBtn, &QPushButton::clicked, this, [this]() { + ui_->infoWidget->setVisible(false); + emit putSetting(ApplicationSettings::ShowInfoWidget, false); + }); +} + +void MainWindow::initChartsView() +{ +/* ui_->widgetChart->init(applicationSettings_, mdProvider_, mdCallbacks_ + , connectionManager_, logMgr_->logger("ui"));*/ +} + +// Initialize widgets related to transactions. +void MainWindow::initTransactionsView() +{ + txModel_ = std::make_shared(logger_, this); + connect(txModel_.get(), &TransactionsViewModel::needTXDetails, this + , &MainWindow::needTXDetails); + + ui_->widgetExplorer->init(logger_); + ui_->widgetTransactions->init(logger_, txModel_); + ui_->widgetTransactions->setEnabled(true); + + ui_->widgetPortfolio->SetTransactionsModel(txModel_); +} + +void MainWindow::onReactivate() +{ + show(); +} + +void MainWindow::raiseWindow() +{ + if (isMinimized()) { + showNormal(); + } else if (isHidden()) { + show(); + } + raise(); + activateWindow(); + setFocus(); +#ifdef Q_OS_WIN + auto hwnd = reinterpret_cast(winId()); + auto flags = static_cast(SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + auto currentProcessId = ::GetCurrentProcessId(); + auto currentThreadId = ::GetCurrentThreadId(); + auto windowThreadId = ::GetWindowThreadProcessId(hwnd, nullptr); + if (currentThreadId != windowThreadId) { + ::AttachThreadInput(windowThreadId, currentThreadId, TRUE); + } + ::AllowSetForegroundWindow(currentProcessId); + ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, flags); + ::SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, flags); + ::SetForegroundWindow(hwnd); + ::SetFocus(hwnd); + ::SetActiveWindow(hwnd); + if (currentThreadId != windowThreadId) { + ::AttachThreadInput(windowThreadId, currentThreadId, FALSE); + } +#endif // Q_OS_WIN +} + +void MainWindow::updateAppearance() +{ + if (!closeToTray_ && isHidden()) { + setWindowState(windowState() & ~Qt::WindowMinimized); + show(); + raise(); + activateWindow(); + } + + setWindowTitle(tr("BlockSettle Terminal")); + +// const auto bsTitle = tr("BlockSettle Terminal [%1]"); +// switch (applicationSettings_->get(ApplicationSettings::netType)) { +// case NetworkType::TestNet: +// setWindowTitle(bsTitle.arg(tr("TESTNET"))); +// break; + +// case NetworkType::RegTest: +// setWindowTitle(bsTitle.arg(tr("REGTEST"))); +// break; + +// default: +// setWindowTitle(tr("BlockSettle Terminal")); +// break; +// } +} + +void MainWindow::onGenerateAddress() +{ + ui_->widgetWallets->onGenerateAddress(ui_->tabWidget->currentWidget() == ui_->widgetWallets); +} + +void MainWindow::onSend() +{ + std::string selectedWalletId; + + if (ui_->tabWidget->currentWidget() == ui_->widgetWallets) { + auto wallet = ui_->widgetWallets->getSelectedHdWallet(); + if (!wallet.ids.empty()) { + selectedWalletId = *wallet.ids.cbegin(); + } + } + + if ((QGuiApplication::keyboardModifiers() & Qt::ShiftModifier) || advTxDlgByDefault_) { + const bool loadFees = true; + txDlg_ = new CreateTransactionDialogAdvanced(loadFees, topBlock_, logger_ + , nullptr, bs::UtxoReservationToken{}, this ); + } else { + txDlg_ = new CreateTransactionDialogSimple(topBlock_, logger_, this); + } + connect(txDlg_, &QDialog::finished, [this](int) { + txDlg_->deleteLater(); + txDlg_ = nullptr; + }); + connect(txDlg_, &CreateTransactionDialog::needWalletsList, this, &MainWindow::needWalletsList); + connect(txDlg_, &CreateTransactionDialog::needFeeLevels, this, &MainWindow::needFeeLevels); + connect(txDlg_, &CreateTransactionDialog::needUTXOs, this, &MainWindow::needUTXOs); + connect(txDlg_, &CreateTransactionDialog::needExtAddresses, this, &MainWindow::needExtAddresses); + connect(txDlg_, &CreateTransactionDialog::needIntAddresses, this, &MainWindow::needIntAddresses); + connect(txDlg_, &CreateTransactionDialog::needUsedAddresses, this, &MainWindow::needUsedAddresses); + connect(txDlg_, &CreateTransactionDialog::needAddrComments, this, &MainWindow::needAddrComments); + connect(txDlg_, &CreateTransactionDialog::needWalletBalances, this, &MainWindow::needWalletBalances); + connect(txDlg_, &CreateTransactionDialog::needSignTX, this, &MainWindow::needSignTX); + connect(txDlg_, &CreateTransactionDialog::needBroadcastZC, this, &MainWindow::needBroadcastZC); + connect(txDlg_, &CreateTransactionDialog::needSetTxComment, this, &MainWindow::needSetTxComment); + connect(txDlg_, &CreateTransactionDialog::needChangeAddress, this, &MainWindow::needChangeAddress); + + txDlg_->initUI(); + if (!selectedWalletId.empty()) { + txDlg_->SelectWallet(selectedWalletId, UiUtils::WalletsTypes::None); + } + txDlg_->exec(); +} + +void MainWindow::setupMenu() +{ + // menu role erquired for OSX only, to place it to first menu item + actLogin_->setMenuRole(QAction::ApplicationSpecificRole); + actLogout_->setMenuRole(QAction::ApplicationSpecificRole); + + + ui_->menuFile->insertAction(ui_->actionSettings, actLogin_); + ui_->menuFile->insertAction(ui_->actionSettings, actLogout_); + + ui_->menuFile->insertSeparator(actLogin_); + ui_->menuFile->insertSeparator(ui_->actionSettings); + +/* AboutDialog *aboutDlg = new AboutDialog(applicationSettings_->get(ApplicationSettings::ChangeLog_Base_Url), this); + auto aboutDlgCb = [aboutDlg] (int tab) { + return [aboutDlg, tab]() { + aboutDlg->setTab(tab); + aboutDlg->show(); + }; + };*/ + + SupportDialog *supportDlg = new SupportDialog(this); + auto supportDlgCb = [supportDlg] (int tab, QString title) { + return [supportDlg, tab, title]() { + supportDlg->setTab(tab); + supportDlg->setWindowTitle(title); + supportDlg->show(); + }; + }; + + connect(ui_->actionCreateNewWallet, &QAction::triggered, this, [ww = ui_->widgetWallets]{ ww->onNewWallet(); }); +// connect(ui_->actionAuthenticationAddresses, &QAction::triggered, this, &MainWindow::openAuthManagerDialog); + connect(ui_->actionSettings, &QAction::triggered, this, [=]() { openConfigDialog(); }); +// connect(ui_->actionAccountInformation, &QAction::triggered, this, &MainWindow::openAccountInfoDialog); +// connect(ui_->actionEnterColorCoinToken, &QAction::triggered, this, &MainWindow::openCCTokenDialog); +/* connect(ui_->actionAbout, &QAction::triggered, aboutDlgCb(0)); + connect(ui_->actionVersion, &QAction::triggered, aboutDlgCb(3));*/ + connect(ui_->actionGuides, &QAction::triggered, supportDlgCb(0, QObject::tr("Guides"))); + connect(ui_->actionVideoTutorials, &QAction::triggered, supportDlgCb(1, QObject::tr("Video Tutorials"))); + connect(ui_->actionContact, &QAction::triggered, supportDlgCb(2, QObject::tr("Support"))); + + //onMatchingLogout(); + +#ifndef Q_OS_MAC + ui_->horizontalFrame->hide(); + ui_->menubar->setCornerWidget(ui_->loginGroupWidget); +#endif + +#ifndef PRODUCTION_BUILD +/* auto envType = static_cast(applicationSettings_->get(ApplicationSettings::envConfiguration).toInt()); + bool isProd = envType == ApplicationSettings::EnvConfiguration::Production; + ui_->prodEnvSettings->setEnabled(!isProd); + ui_->testEnvSettings->setEnabled(isProd);*/ + connect(ui_->prodEnvSettings, &QPushButton::clicked, this, [this] { + promptSwitchEnv(true); + }); + connect(ui_->testEnvSettings, &QPushButton::clicked, this, [this] { + promptSwitchEnv(false); + }); +#else + ui_->prodEnvSettings->setVisible(false); + ui_->testEnvSettings->setVisible(false); +#endif // !PRODUCTION_BUILD +} + +void MainWindow::openConfigDialog(bool showInNetworkPage) +{ + cfgDlg_ = new ConfigDialog(this); + connect(cfgDlg_, &QDialog::finished, [this](int) { + cfgDlg_->deleteLater(); + cfgDlg_ = nullptr; + }); + connect(cfgDlg_, &ConfigDialog::reconnectArmory, this, &MainWindow::needArmoryReconnect); + connect(cfgDlg_, &ConfigDialog::putSetting, this, &MainWindow::putSetting); + connect(cfgDlg_, &ConfigDialog::resetSettings, this, &MainWindow::resetSettings); + connect(cfgDlg_, &ConfigDialog::resetSettingsToState, this, &MainWindow::resetSettingsToState); + connect(cfgDlg_, &ConfigDialog::resetSettingsToState, this, &MainWindow::resetSettingsToState); + connect(cfgDlg_, &ConfigDialog::setArmoryServer, this, &MainWindow::setArmoryServer); + connect(cfgDlg_, &ConfigDialog::addArmoryServer, this, &MainWindow::addArmoryServer); + connect(cfgDlg_, &ConfigDialog::delArmoryServer, this, &MainWindow::delArmoryServer); + connect(cfgDlg_, &ConfigDialog::updArmoryServer, this, &MainWindow::updArmoryServer); + connect(cfgDlg_, &ConfigDialog::setSigner, this, &MainWindow::setSigner); + + emit needSettingsState(); + emit needArmoryServers(); + emit needSigners(); + + if (showInNetworkPage) { + cfgDlg_->popupNetworkSettings(); + } + cfgDlg_->exec(); +} + +void MainWindow::onLoginInitiated() +{ + if (!actLogin_->isEnabled()) { + return; + } + emit needOpenBsConnection(); +} + +void bs::gui::qt::MainWindow::onLoginStarted(const std::string& login, bool success, const std::string& errMsg) +{ +} + +void MainWindow::onAccountTypeChanged(bs::network::UserType userType, bool enabled) +{ +// userType_ = userType; + if ((accountEnabled_ != enabled) && (userType != bs::network::UserType::Chat)) { + notifCenter_->enqueue(enabled ? bs::ui::NotifyType::AccountEnabled + : bs::ui::NotifyType::AccountDisabled, {}); + } +} + +void bs::gui::qt::MainWindow::onLogoutInitiated() +{ + ui_->widgetWallets->setUsername(QString()); +// mdProvider_->UnsubscribeFromMD(); + + setLoginButtonText(loginButtonText_); + + setWidgetsAuthorized(false); +} + +void MainWindow::onLoggedOut() +{ + currentUserLogin_.clear(); + emit needMatchingLogout(); +} + +void MainWindow::onMDUpdated(bs::network::Asset::Type assetType + , const QString& security, const bs::network::MDFields &fields) +{ + //ui_->widgetPortfolio->onMDUpdated(assetType, security, fields); +} + +void bs::gui::qt::MainWindow::onBalance(const std::string& currency, double balance) +{ + statusBarView_->onBalanceUpdated(currency, balance); + ui_->widgetPortfolio->onBalance(currency, balance); +} + +void MainWindow::onReservedUTXOs(const std::string& resId + , const std::string& subId, const std::vector& utxos) +{} + +bs::gui::WalletSeedData MainWindow::getWalletSeed(const std::string& rootId) const +{ + auto seedDialog = new bs::gui::qt::SeedDialog(rootId, (QWidget*)this); + const int rc = seedDialog->exec(); + seedDialog->deleteLater(); + if (rc == QDialog::Accepted) { + return seedDialog->getData(); + } + return {}; +} + +bs::gui::WalletSeedData MainWindow::importWallet(const std::string& rootId) const +{ + auto seedDialog = new bs::gui::qt::ImportWalletDialog(rootId, (QWidget*)this); + const int rc = seedDialog->exec(); + seedDialog->deleteLater(); + if (rc == QDialog::Accepted) { + return seedDialog->getData(); + } + return {}; +} + +bool bs::gui::qt::MainWindow::deleteWallet(const std::string& rootId, const std::string& name) const +{ + BSMessageBox mBox(BSMessageBox::question, tr("Wallet delete") + , tr("Are you sure you want to delete wallet %1 with id %2?") + .arg(QString::fromStdString(name)).arg(QString::fromStdString(rootId)) + , (QWidget*)this); + mBox.setConfirmButtonText(tr("Yes")); + mBox.setCancelButtonText(tr("No")); + return (mBox.exec() == QDialog::Accepted); +} + +void MainWindow::showRunInBackgroundMessage() +{ + sysTrayIcon_->showMessage(tr("BlockSettle is running") + , tr("BlockSettle Terminal is running in the backgroud. Click the tray icon to open the main window.") + , QIcon(QLatin1String(":/resources/login-logo.png"))); +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + emit putSetting(ApplicationSettings::GUI_main_geometry, geometry()); + emit putSetting(ApplicationSettings::GUI_main_tab, ui_->tabWidget->currentIndex()); + +/* if (applicationSettings_->get(ApplicationSettings::closeToTray)) { + hide(); + event->ignore(); + } + else { + if (chatClientServicePtr_) { + chatClientServicePtr_->LogoutFromServer(); + } + */ + QMainWindow::closeEvent(event); + QTimer::singleShot(100, [] { QApplication::exit(); }); +// } +} + +void MainWindow::changeEvent(QEvent* e) +{ + switch (e->type()) { + case QEvent::WindowStateChange: + if (this->windowState() & Qt::WindowMinimized) { +/* if (applicationSettings_->get(ApplicationSettings::minimizeToTray)) + { + QTimer::singleShot(0, this, &QMainWindow::hide); + }*/ + } + break; + default: + break; + } + QMainWindow::changeEvent(e); +} + +void MainWindow::setLoginButtonText(const QString &text) +{ + auto *button = ui_->pushButtonUser; + button->setText(text); + button->setProperty("usernameButton", QVariant(text == loginButtonText_)); + button->setProperty("usernameButtonLoggedIn", QVariant(text != loginButtonText_)); + button->style()->unpolish(button); + button->style()->polish(button); + button->update(); + +#ifndef Q_OS_MAC + ui_->menubar->adjustSize(); +#endif +} + +void MainWindow::setupShortcuts() +{ + auto overviewTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+1")), this); + overviewTabShortcut->setContext(Qt::WindowShortcut); + connect(overviewTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(0);}); + + auto tradingTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+2")), this); + tradingTabShortcut->setContext(Qt::WindowShortcut); + connect(tradingTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(1);}); + + auto dealingTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+3")), this); + dealingTabShortcut->setContext(Qt::WindowShortcut); + connect(dealingTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(2);}); + + auto walletsTabShortcutt = new QShortcut(QKeySequence(QStringLiteral("Ctrl+4")), this); + walletsTabShortcutt->setContext(Qt::WindowShortcut); + connect(walletsTabShortcutt, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(3);}); + + auto transactionsTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+5")), this); + transactionsTabShortcut->setContext(Qt::WindowShortcut); + connect(transactionsTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(4);}); + + auto explorerTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+6")), this); + explorerTabShortcut->setContext(Qt::WindowShortcut); + connect(explorerTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(5);}); + + auto chartsTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+7")), this); + chartsTabShortcut->setContext(Qt::WindowShortcut); + connect(chartsTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(6);}); + + auto chatTabShortcut = new QShortcut(QKeySequence(QStringLiteral("Ctrl+8")), this); + chatTabShortcut->setContext(Qt::WindowShortcut); + connect(chatTabShortcut, &QShortcut::activated, [this](){ ui_->tabWidget->setCurrentIndex(7);}); + + // TODO: Switch ChatWidget to TabWithShortcut if needed (it will ignore shortcuts right now) + + auto addShotcut = [this](const char *keySequence, TabWithShortcut::ShortcutType type) { + auto shortcut = new QShortcut(QKeySequence(QLatin1String(keySequence)), this); + shortcut->setContext(Qt::WindowShortcut); + connect(shortcut, &QShortcut::activated, [this, type]() { + auto widget = dynamic_cast(ui_->tabWidget->currentWidget()); + if (widget) { + widget->shortcutActivated(type); + } + }); + }; + + addShotcut("Alt+1", TabWithShortcut::ShortcutType::Alt_1); + addShotcut("Alt+2", TabWithShortcut::ShortcutType::Alt_2); + addShotcut("Alt+3", TabWithShortcut::ShortcutType::Alt_3); + addShotcut("Ctrl+S", TabWithShortcut::ShortcutType::Ctrl_S); + addShotcut("Ctrl+P", TabWithShortcut::ShortcutType::Ctrl_P); + addShotcut("Ctrl+Q", TabWithShortcut::ShortcutType::Ctrl_Q); + addShotcut("Alt+S", TabWithShortcut::ShortcutType::Alt_S); + addShotcut("Alt+B", TabWithShortcut::ShortcutType::Alt_B); + addShotcut("Alt+P", TabWithShortcut::ShortcutType::Alt_P); +} + +void MainWindow::onButtonUserClicked() { + if (ui_->pushButtonUser->text() == loginButtonText_) { + onLoginInitiated(); + } else { + if (BSMessageBox(BSMessageBox::question, tr("User Logout"), tr("You are about to logout") + , tr("Do you want to continue?")).exec() == QDialog::Accepted) + onLoggedOut(); + } +} + +/*void MainWindow::onTabWidgetCurrentChanged(const int &index) +{ + const int chatIndex = ui_->tabWidget->indexOf(ui_->widgetChat); + const bool isChatTab = index == chatIndex; + //ui_->widgetChat->updateChat(isChatTab); +}*/ + +void MainWindow::onSignerVisibleChanged() +{ + processDeferredDialogs(); +} + +void MainWindow::initWidgets() +{ + ui_->widgetWallets->init(logger_); + connect(ui_->widgetWallets, &WalletsWidget::newWalletCreationRequest, this, &MainWindow::createNewWallet); + connect(ui_->widgetWallets, &WalletsWidget::needHDWalletDetails, this, &MainWindow::needHDWalletDetails); + connect(ui_->widgetWallets, &WalletsWidget::needWalletBalances, this, &MainWindow::needWalletBalances); + connect(ui_->widgetWallets, &WalletsWidget::needUTXOs, this, &MainWindow::needUTXOs); + connect(ui_->widgetWallets, &WalletsWidget::needExtAddresses, this, &MainWindow::needExtAddresses); + connect(ui_->widgetWallets, &WalletsWidget::needIntAddresses, this, &MainWindow::needIntAddresses); + connect(ui_->widgetWallets, &WalletsWidget::needUsedAddresses, this, &MainWindow::needUsedAddresses); + connect(ui_->widgetWallets, &WalletsWidget::needAddrComments, this, &MainWindow::needAddrComments); + connect(ui_->widgetWallets, &WalletsWidget::setAddrComment, this, &MainWindow::setAddrComment); + connect(ui_->widgetWallets, &WalletsWidget::needLedgerEntries, this, &MainWindow::needLedgerEntries); + connect(ui_->widgetWallets, &WalletsWidget::needTXDetails, this, &MainWindow::needTXDetails); + connect(ui_->widgetWallets, &WalletsWidget::needWalletDialog, this, &MainWindow::needWalletDialog); + connect(ui_->widgetWallets, &WalletsWidget::createExtAddress, this, &MainWindow::createExtAddress); + + connect(ui_->widgetExplorer, &ExplorerWidget::needAddressHistory, this, &MainWindow::needAddressHistory); + connect(ui_->widgetExplorer, &ExplorerWidget::needTXDetails, this, &MainWindow::needTXDetails); + + initTransactionsView(); + + ui_->widgetPortfolio->init(logger_); + //connect(ui_->widgetPortfolio, &PortfolioWidget::needMdConnection, this, &MainWindow::needMdConnection); + //connect(ui_->widgetPortfolio, &PortfolioWidget::needMdDisconnect, this, &MainWindow::needMdDisconnect); + + //orderListModel_ = std::make_shared(this); + dialogMgr_ = std::make_shared(this); +} + +void MainWindow::promptSwitchEnv(bool prod) +{ + BSMessageBox mbox(BSMessageBox::question + , tr("Environment selection") + , tr("Switch Environment") + , tr("Do you wish to change to the %1 environment now?").arg(prod ? tr("Production") : tr("Test")) + , this); + mbox.setConfirmButtonText(tr("Yes")); + int rc = mbox.exec(); + if (rc == QDialog::Accepted) { + if (prod) { + switchToProdEnv(); + } else { + switchToTestEnv(); + } + restartTerminal(); + } +} + +void MainWindow::switchToTestEnv() +{ +/* applicationSettings_->set(ApplicationSettings::envConfiguration + , static_cast(ApplicationSettings::EnvConfiguration::Test)); + armoryServersProvider_->setupServer(armoryServersProvider_->getIndexOfTestNetServer());*/ +} + +void MainWindow::switchToProdEnv() +{ +/* applicationSettings_->set(ApplicationSettings::envConfiguration + , static_cast(ApplicationSettings::EnvConfiguration::Production)); + armoryServersProvider_->setupServer(armoryServersProvider_->getIndexOfMainNetServer());*/ +} + +void MainWindow::restartTerminal() +{ +// lockFile_.unlock(); + QProcess::startDetached(qApp->arguments()[0], qApp->arguments()); + qApp->quit(); +} + +void MainWindow::processDeferredDialogs() +{ + if(deferredDialogRunning_) { + return; + } +/* if (signContainer_ && signContainer_->isLocal() && signContainer_->isWindowVisible()) { + return; + }*/ + + deferredDialogRunning_ = true; + while (!deferredDialogs_.empty()) { + deferredDialogs_.front()(); // run stored lambda + deferredDialogs_.pop(); + } + deferredDialogRunning_ = false; +} + +void MainWindow::addDeferredDialog(const std::function &deferredDialog) +{ + // multi thread scope, it's safe to call this function from different threads + QMetaObject::invokeMethod(this, [this, deferredDialog] { + // single thread scope (main thread), it's safe to push to deferredDialogs_ + // and check deferredDialogRunning_ variable + deferredDialogs_.push(deferredDialog); + processDeferredDialogs(); + }, Qt::QueuedConnection); +} diff --git a/GUI/QtWidgets/MainWindow.h b/GUI/QtWidgets/MainWindow.h new file mode 100644 index 000000000..f2ff40c34 --- /dev/null +++ b/GUI/QtWidgets/MainWindow.h @@ -0,0 +1,285 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef __GUI_QT_MAIN_WINDOW_H__ +#define __GUI_QT_MAIN_WINDOW_H__ + +#include +#include +#include +#include "Address.h" +#include "ArmoryConnection.h" +#include "AuthAddress.h" +#include "CommonTypes.h" +#include "SeedDialog.h" +#include "Wallets/SignContainer.h" +#include "Settings/SignersProvider.h" +#include "UiUtils.h" + +namespace spdlog { + class logger; +} +namespace Ui { + class BSTerminalMainWindow; +} +namespace bs { + namespace message { + class QueueInterface; + class User; + } +} + +class AboutDialog; +class AuthAddressDialog; +class ConfigDialog; +class CreateTransactionDialog; +class DialogManager; +class LoginWindow; +class NotificationCenter; +class OrderListModel; +class QSystemTrayIcon; +class StatusBarView; +class TransactionsViewModel; + +namespace bs { + namespace gui { + namespace qt { + class MainWindow : public QMainWindow + { + Q_OBJECT + + public: + MainWindow(const std::shared_ptr & + , const std::shared_ptr & + , const std::shared_ptr < bs::message::User> &); + ~MainWindow() override; + + void onSetting(int setting, const QVariant &value); + void onSettingsState(const ApplicationSettings::State&); + void onGetGeometry(const QRect &); + void showStartupDialog(bool showLic); + + void onArmoryStateChanged(int state, unsigned int blockNum); + void onNewBlock(int state, unsigned int blockNum); + void onSignerStateChanged(int state, const std::string &); + void onWalletsReady(); + + void onHDWallet(const bs::sync::WalletInfo &); + void onWalletDeleted(const bs::sync::WalletInfo&); + void onHDWalletDetails(const bs::sync::HDWalletData &); + void onWalletsList(const std::string &id, const std::vector&); + void onWalletData(const std::string &walletId, const bs::sync::WalletData&); + void onAddresses(const std::string& walletId, const std::vector &); + void onAddressComments(const std::string &walletId + , const std::map &); + void onWalletBalance(const bs::sync::WalletBalanceData &); + void onLedgerEntries(const std::string &filter, uint32_t totalPages + , uint32_t curPage, uint32_t curBlock, const std::vector &); + void onTXDetails(const std::vector &); + void onNewZCs(const std::vector&); + void onZCsInvalidated(const std::vector& txHashes); + void onAddressHistory(const bs::Address&, uint32_t curBlock + , const std::vector&); + void onChangeAddress(const std::string& walletId, const bs::Address&); + + void onFeeLevels(const std::map&); + void onUTXOs(const std::string& id, const std::string& walletId, const std::vector&); + void onSignedTX(const std::string &id, BinaryData signedTX, bs::error::ErrorCode result); + void onArmoryServers(const QList&, int idxCur, int idxConn); + void onSignerSettings(const QList&, const std::string& ownKey, int idxCur); + + void onLoginStarted(const std::string &login, bool success, const std::string &errMsg); + //void onLoggedIn(const BsClientLoginResult&); + void onAccountTypeChanged(bs::network::UserType userType, bool enabled); + //void onMatchingLogin(const std::string& mtchLogin, BaseCelerClient::CelerUserType + // , const std::string &userId); + //void onMatchingLogout(); + + //void onMDConnected(); + //void onMDDisconnected(); + //void onNewSecurity(const std::string& name, bs::network::Asset::Type); + void onMDUpdated(bs::network::Asset::Type assetType + , const QString& security, const bs::network::MDFields &); + void onBalance(const std::string& currency, double balance); + + //void onAuthKey(const bs::Address&, const BinaryData& authKey); + + //void onSettlementPending(const std::string& rfqId, const std::string& quoteId + // , const BinaryData& settlementId, int timeLeftMS); + //void onSettlementComplete(const std::string& rfqId, const std::string& quoteId + // , const BinaryData& settlementId); + //void onOrdersUpdate(const std::vector&); + + void onReservedUTXOs(const std::string& resId, const std::string& subId + , const std::vector&); + + bs::gui::WalletSeedData getWalletSeed(const std::string& rootId) const; + bs::gui::WalletSeedData importWallet(const std::string& rootId) const; + bool deleteWallet(const std::string& rootId, const std::string& name) const; + + public slots: + void onReactivate(); + void raiseWindow(); + + signals: + void getSettings(const std::vector &); + void putSetting(ApplicationSettings::Setting, const QVariant &); + void resetSettings(const std::vector &); + void resetSettingsToState(const ApplicationSettings::State&); + void needSettingsState(); + void needArmoryServers(); + void setArmoryServer(int); + void addArmoryServer(const ArmoryServer&); + void delArmoryServer(int); + void updArmoryServer(int, const ArmoryServer&); + void needArmoryReconnect(); + void needSigners(); + void setSigner(int); + void createNewWallet(); + void needHDWalletDetails(const std::string &walletId); + void needWalletsList(UiUtils::WalletsTypes, const std::string &id); + void needWalletBalances(const std::string &walletId); + void needWalletData(const std::string& walletId); + void needWalletDialog(bs::signer::ui::GeneralDialogType, const std::string& rootId); + + void createExtAddress(const std::string& walletId); + void needExtAddresses(const std::string &walletId); + void needChangeAddress(const std::string& walletId); + void needIntAddresses(const std::string &walletId); + void needUsedAddresses(const std::string &walletId); + void needAddrComments(const std::string &walletId, const std::vector &); + void setAddrComment(const std::string &walletId, const bs::Address & + , const std::string &comment); + + void needLedgerEntries(const std::string &filter); + void needTXDetails(const std::vector& + , bool useCache=true, const bs::Address& addr = {}); + void needAddressHistory(const bs::Address&); + + void needFeeLevels(const std::vector&); + void needUTXOs(const std::string& id, const std::string& walletId + , bool confOnly = false, bool swOnly = false); + + void needSignTX(const std::string& id, const bs::core::wallet::TXSignRequest& + , bool keepDupRecips = false, SignContainer::TXSignMode mode = SignContainer::TXSignMode::Full + , const SecureBinaryData& passphrase = {}); + void needBroadcastZC(const std::string& id, const BinaryData&); + void needSetTxComment(const std::string& walletId, const BinaryData& txHash, const std::string& comment); + + void needOpenBsConnection(); + void needCloseBsConnection(); + void needStartLogin(const std::string& login); + void needCancelLogin(); + void needMatchingLogout(); + void needMdConnection(ApplicationSettings::EnvConfiguration); + void needMdDisconnect(); + + void needReserveUTXOs(const std::string& reserveId, const std::string& subId + , uint64_t amount, bool withZC = false, const std::vector& utxos = {}); + void needUnreserveUTXOs(const std::string& reserveId, const std::string& subId); + + private slots: + void onSend(); + void onGenerateAddress(); + + void openConfigDialog(bool showInNetworkPage = false); + + void onLoginInitiated(); + void onLogoutInitiated(); + void onLoggedOut(); + void onButtonUserClicked(); + +// void onCelerConnected(); +// void onCelerDisconnected(); +// void onCelerConnectionError(int errorCode); + void showRunInBackgroundMessage(); + +// void onBsConnectionDisconnected(); +// void onBsConnectionFailed(); + + void onSignerVisibleChanged(); + + protected: + bool event(QEvent *) override; + void closeEvent(QCloseEvent *) override; + void changeEvent(QEvent *) override; + + private: + void setLoginButtonText(const QString& text); + + void setupShortcuts(); + void setupInfoWidget(); + void setupIcon(); + void setupToolbar(); + void setupMenu(); + void setupTopRightWidget(); + + void updateAppearance(); + void setWidgetsAuthorized(bool); + + void initWidgets(); + void initTransactionsView(); + void initChartsView(); + + void promptSwitchEnv(bool prod); + void switchToTestEnv(); + void switchToProdEnv(); + + void restartTerminal(); + void addDeferredDialog(const std::function &); + void processDeferredDialogs(); + + //void activateClient(const BsClientLoginResult&); + + private: + std::unique_ptr ui_; + std::shared_ptr logger_; + std::shared_ptr queue_; + std::shared_ptr guiUser_, settingsUser_; + + QAction *actSend_{ nullptr }; + QAction *actNewAddress_{ nullptr }; + QAction *actLogin_{ nullptr }; + QAction *actLogout_{ nullptr }; + + std::shared_ptr statusBarView_; + std::shared_ptr sysTrayIcon_; + std::shared_ptr notifCenter_; + std::shared_ptr txModel_; + //std::shared_ptr orderListModel_; + + std::shared_ptr dialogMgr_; + CreateTransactionDialog* txDlg_{ nullptr }; + ConfigDialog* cfgDlg_{ nullptr }; + + // std::shared_ptr walletsWizard_; + + bool accountEnabled_{ false }; + QString currentUserLogin_; + QString loginButtonText_; + QTimer * loginTimer_{}; + + bool closeToTray_{ false }; + bool initialWalletCreateDialogShown_ = false; + bool deferCCsync_ = false; + bool advTxDlgByDefault_{ false }; + ApplicationSettings::EnvConfiguration envConfig_{ ApplicationSettings::EnvConfiguration::Unknown }; + + std::queue> deferredDialogs_; + bool deferredDialogRunning_ = false; + // bs::network::UserType userType_{}; + + uint32_t topBlock_{ 0 }; + }; + } + } +} // namespaces + +#endif // __GUI_QT_MAIN_WINDOW_H__ diff --git a/GUI/QtWidgets/QtGuiAdapter.cpp b/GUI/QtWidgets/QtGuiAdapter.cpp new file mode 100644 index 000000000..b494fe42b --- /dev/null +++ b/GUI/QtWidgets/QtGuiAdapter.cpp @@ -0,0 +1,1684 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "CommonTypes.h" +#include "QtGuiAdapter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AppNap.h" +#include "BSMessageBox.h" +#include "BSTerminalSplashScreen.h" +#include "MainWindow.h" +#include "MessageUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" +#include "SettingsAdapter.h" +#include "TradesVerification.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + +#if defined (Q_OS_MAC) +class MacOsApp : public QApplication +{ + Q_OBJECT +public: + MacOsApp(int &argc, char **argv) : QApplication(argc, argv) {} + ~MacOsApp() override = default; + +signals: + void reactivateTerminal(); + +protected: + bool event(QEvent* ev) override + { + if (ev->type() == QEvent::ApplicationStateChange) { + auto appStateEvent = static_cast(ev); + + if (appStateEvent->applicationState() == Qt::ApplicationActive) { + if (activationRequired_) { + emit reactivateTerminal(); + } else { + activationRequired_ = true; + } + } else { + activationRequired_ = false; + } + } + + return QApplication::event(ev); + } + +private: + bool activationRequired_ = false; +}; +#endif // Q_OS_MAC + + +static void checkStyleSheet(QApplication &app) +{ + QLatin1String styleSheetFileName = QLatin1String("stylesheet.css"); + + QFileInfo info = QFileInfo(QLatin1String(styleSheetFileName)); + + static QDateTime lastTimestamp = info.lastModified(); + + if (lastTimestamp == info.lastModified()) { + return; + } + + lastTimestamp = info.lastModified(); + + QFile stylesheetFile(styleSheetFileName); + + bool result = stylesheetFile.open(QFile::ReadOnly); + assert(result); + + app.setStyleSheet(QString::fromLatin1(stylesheetFile.readAll())); +} + +static QScreen *getDisplay(QPoint position) +{ + for (auto currentScreen : QGuiApplication::screens()) { + if (currentScreen->availableGeometry().contains(position, false)) { + return currentScreen; + } + } + + return QGuiApplication::primaryScreen(); +} + + +QtGuiAdapter::QtGuiAdapter(const std::shared_ptr &logger) + : QObject(nullptr), logger_(logger) + , userSettings_(std::make_shared(TerminalUsers::Settings)) + , userWallets_(std::make_shared(TerminalUsers::Wallets)) + , userBlockchain_(std::make_shared(TerminalUsers::Blockchain)) + , userSigner_(std::make_shared(TerminalUsers::Signer)) + , userBS_(std::make_shared(TerminalUsers::BsServer)) + , userMatch_(std::make_shared(TerminalUsers::Matching)) + , userSettl_(std::make_shared(TerminalUsers::Settlement)) + , userMD_(std::make_shared(TerminalUsers::MktData)) + , userTrk_(std::make_shared(TerminalUsers::OnChainTracker)) +{} + +QtGuiAdapter::~QtGuiAdapter() +{} + +void QtGuiAdapter::run(int &argc, char **argv) +{ + logger_->debug("[QtGuiAdapter::run]"); + + Q_INIT_RESOURCE(armory); + Q_INIT_RESOURCE(tradinghelp); + Q_INIT_RESOURCE(wallethelp); + + QApplication::setAttribute(Qt::AA_DontShowIconsInMenus); + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + +#if defined (Q_OS_MAC) + MacOsApp app(argc, argv); +#else + QApplication app(argc, argv); +#endif + + QApplication::setQuitOnLastWindowClosed(false); + + const QFileInfo localStyleSheetFile(QLatin1String("stylesheet.css")); + QFile stylesheetFile(localStyleSheetFile.exists() + ? localStyleSheetFile.fileName() : QLatin1String(":/STYLESHEET")); + + if (stylesheetFile.open(QFile::ReadOnly)) { + app.setStyleSheet(QString::fromLatin1(stylesheetFile.readAll())); + QPalette p = QApplication::palette(); + p.setColor(QPalette::Disabled, QPalette::Light, QColor(10, 22, 25)); + QApplication::setPalette(p); + } + +#ifndef NDEBUG + // Start monitoring to update stylesheet live when file is changed on the disk + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, &app, [&app] { + checkStyleSheet(app); + }); + timer.start(100); +#endif + + QDirIterator it(QLatin1String(":/resources/Raleway/")); + while (it.hasNext()) { + QFontDatabase::addApplicationFont(it.next()); + } + + QString location = QStandardPaths::writableLocation(QStandardPaths::TempLocation); +#ifndef NDEBUG + QString userName = QDir::home().dirName(); + QString lockFilePath = location + QLatin1String("/blocksettle-") + userName + QLatin1String(".lock"); +#else + QString lockFilePath = location + QLatin1String("/blocksettle.lock"); +#endif + QLockFile lockFile(lockFilePath); + lockFile.setStaleLockTime(0); + + if (!lockFile.tryLock()) { + BSMessageBox box(BSMessageBox::info, app.tr("BlockSettle Terminal") + , app.tr("BlockSettle Terminal is already running") + , app.tr("Stop the other BlockSettle Terminal instance. If no other " \ + "instance is running, delete the lockfile (%1).").arg(lockFilePath)); + box.exec(); + return; + } + + QString logoIcon; + logoIcon = QLatin1String(":/SPLASH_LOGO"); + + QPixmap splashLogo(logoIcon); + const int splashScreenWidth = 400; + { + std::lock_guard lock(mutex_); + splashScreen_ = new BSTerminalSplashScreen(splashLogo.scaledToWidth(splashScreenWidth + , Qt::SmoothTransformation)); + } + updateSplashProgress(); + splashScreen_->show(); + + logger_->debug("[QtGuiAdapter::run] creating main window"); + mainWindow_ = new bs::gui::qt::MainWindow(logger_, queue_, user_); + logger_->debug("[QtGuiAdapter::run] start main window connections"); + makeMainWinConnections(); + updateStates(); + + requestInitialSettings(); + logger_->debug("[QtGuiAdapter::run] initial setup done"); + +#if defined (Q_OS_MAC) + MacOsApp *macApp = (MacOsApp*)(app); + QObject::connect(macApp, &MacOsApp::reactivateTerminal, mainWindow + , &bs::gui::qt::MainWindow::onReactivate); +#endif + bs::disableAppNap(); + + if (app.exec() != 0) { + throw std::runtime_error("application execution failed"); + } +} + +bool QtGuiAdapter::process(const Envelope &env) +{ + if (std::dynamic_pointer_cast(env.sender)) { + switch (env.sender->value()) { + case TerminalUsers::Settings: + return processSettings(env); + case TerminalUsers::Blockchain: + return processBlockchain(env); + case TerminalUsers::Signer: + return processSigner(env); + case TerminalUsers::Wallets: + return processWallets(env); + case TerminalUsers::BsServer: + return processBsServer(env); + case TerminalUsers::Matching: + return processMatching(env); + case TerminalUsers::MktData: + return processMktData(env); + case TerminalUsers::OnChainTracker: + return processOnChainTrack(env); + case TerminalUsers::Assets: + return processAssets(env); + default: break; + } + } + return true; +} + +bool QtGuiAdapter::processBroadcast(const bs::message::Envelope& env) +{ + if (std::dynamic_pointer_cast(env.sender)) { + switch (env.sender->value()) { + case TerminalUsers::System: + return processAdminMessage(env); + case TerminalUsers::Settings: + return processSettings(env); + case TerminalUsers::Blockchain: + return processBlockchain(env); + case TerminalUsers::Signer: + return processSigner(env); + case TerminalUsers::Wallets: + return processWallets(env); + case TerminalUsers::BsServer: + return processBsServer(env); + case TerminalUsers::Matching: + return processMatching(env); + case TerminalUsers::MktData: + return processMktData(env); + case TerminalUsers::OnChainTracker: + return processOnChainTrack(env); + case TerminalUsers::Assets: + return processAssets(env); + default: break; + } + } + return false; +} + +bool QtGuiAdapter::processSettings(const Envelope &env) +{ + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kGetResponse: + return processSettingsGetResponse(msg.get_response()); + case SettingsMessage::kSettingsUpdated: + return processSettingsGetResponse(msg.settings_updated()); + case SettingsMessage::kState: + return processSettingsState(msg.state()); + case SettingsMessage::kArmoryServers: + return processArmoryServers(msg.armory_servers()); + default: break; + } + return true; +} + +bool QtGuiAdapter::processSettingsGetResponse(const SettingsMessage_SettingsResponse &response) +{ + std::map settings; + for (const auto &setting : response.responses()) { + switch (setting.request().index()) { + case SetIdx_GUI_MainGeom: { + QRect mainGeometry(setting.rect().left(), setting.rect().top() + , setting.rect().width(), setting.rect().height()); + if (splashScreen_) { + const auto ¤tDisplay = getDisplay(mainGeometry.center()); + auto splashGeometry = splashScreen_->geometry(); + splashGeometry.moveCenter(currentDisplay->geometry().center()); + QMetaObject::invokeMethod(splashScreen_, [ss=splashScreen_, splashGeometry] { + ss->setGeometry(splashGeometry); + }); + } + QMetaObject::invokeMethod(mainWindow_, [mw=mainWindow_, mainGeometry] { + mw->onGetGeometry(mainGeometry); + }); + } + break; + + case SetIdx_Initialized: + if (!setting.b()) { +#ifdef _WIN32 + // Read registry value in case it was set with installer. Could be used only on Windows for now. + QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\blocksettle\\blocksettle"), QSettings::NativeFormat); + bool showLicense = !settings.value(QLatin1String("license_accepted"), false).toBool(); +#else + bool showLicense = true; +#endif // _WIN32 + QMetaObject::invokeMethod(mainWindow_, [mw = mainWindow_, showLicense] { + mw->showStartupDialog(showLicense); + }); + onResetSettings({}); + } + break; + + default: + settings[setting.request().index()] = fromResponse(setting); + break; + } + } + if (!settings.empty()) { + return QMetaObject::invokeMethod(mainWindow_, [mw = mainWindow_, settings] { + for (const auto& setting : settings) { + mw->onSetting(setting.first, setting.second); + } + }); + } + return true; +} + +bool QtGuiAdapter::processSettingsState(const SettingsMessage_SettingsResponse& response) +{ + ApplicationSettings::State state; + for (const auto& setting : response.responses()) { + state[static_cast(setting.request().index())] = + fromResponse(setting); + } + return QMetaObject::invokeMethod(mainWindow_, [mw = mainWindow_, state] { + mw->onSettingsState(state); + }); +} + +bool QtGuiAdapter::processArmoryServers(const SettingsMessage_ArmoryServers& response) +{ + QList servers; + for (const auto& server : response.servers()) { + servers << ArmoryServer{ QString::fromStdString(server.server_name()) + , static_cast(server.network_type()) + , QString::fromStdString(server.server_address()) + , std::stoi(server.server_port()), QString::fromStdString(server.server_key()) + , SecureBinaryData::fromString(server.password()) + , server.run_locally(), server.one_way_auth() }; + } + logger_->debug("[{}] {} servers, cur: {}, conn: {}", __func__, servers.size() + , response.idx_current(), response.idx_connected()); + return QMetaObject::invokeMethod(mainWindow_, [mw = mainWindow_, servers, response] { + mw->onArmoryServers(servers, response.idx_current(), response.idx_connected()); + }); +} + +bool QtGuiAdapter::processAdminMessage(const Envelope &env) +{ + AdministrativeMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse admin msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case AdministrativeMessage::kComponentCreated: + switch (static_cast(msg.component_created())) { + case TerminalUsers::Unknown: + case TerminalUsers::API: + case TerminalUsers::Settings: + break; + default: + createdComponents_.insert(msg.component_created()); + break; + } + break; + case AdministrativeMessage::kComponentLoading: { + std::lock_guard lock(mutex_); + loadingComponents_.insert(msg.component_loading()); + break; + } + default: break; + } + updateSplashProgress(); + return true; +} + +bool QtGuiAdapter::processBlockchain(const Envelope &env) +{ + ArmoryMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[QtGuiAdapter::processBlockchain] failed to parse msg #{}" + , env.foreignId()); + if (!env.receiver) { + logger_->debug("[{}] no receiver", __func__); + } + return true; + } + switch (msg.data_case()) { + case ArmoryMessage::kLoading: + loadingComponents_.insert(env.sender->value()); + updateSplashProgress(); + break; + case ArmoryMessage::kStateChanged: + armoryState_ = msg.state_changed().state(); + blockNum_ = msg.state_changed().top_block(); + if (mainWindow_) { + QMetaObject::invokeMethod(mainWindow_, [this, state=msg.state_changed()] { + mainWindow_->onArmoryStateChanged(state.state(), state.top_block()); + }); + } + break; + case ArmoryMessage::kNewBlock: + blockNum_ = msg.new_block().top_block(); + if (mainWindow_) { + QMetaObject::invokeMethod(mainWindow_, [this]{ + mainWindow_->onNewBlock(armoryState_, blockNum_); + }); + } + break; + case ArmoryMessage::kWalletRegistered: + if (msg.wallet_registered().success() && msg.wallet_registered().wallet_id().empty()) { + walletsReady_ = true; + QMetaObject::invokeMethod(mainWindow_, [this] { + mainWindow_->onWalletsReady(); + }); + } + break; + case ArmoryMessage::kAddressHistory: + return processAddressHist(msg.address_history()); + case ArmoryMessage::kFeeLevelsResponse: + return processFeeLevels(msg.fee_levels_response()); + case ArmoryMessage::kZcReceived: + return processZC(msg.zc_received()); + case ArmoryMessage::kZcInvalidated: + return processZCInvalidated(msg.zc_invalidated()); + default: break; + } + return true; +} + +bool QtGuiAdapter::processSigner(const Envelope &env) +{ + SignerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[QtGuiAdapter::processSigner] failed to parse msg #{}" + , env.foreignId()); + if (!env.receiver) { + logger_->debug("[{}] no receiver", __func__); + } + return true; + } + switch (msg.data_case()) { + case SignerMessage::kState: + signerState_ = msg.state().code(); + signerDetails_ = msg.state().text(); + if (mainWindow_) { + QMetaObject::invokeMethod(mainWindow_, [this]{ + mainWindow_->onSignerStateChanged(signerState_, signerDetails_); + }); + } + break; + case SignerMessage::kNeedNewWalletPrompt: + createWallet(true); + break; + case SignerMessage::kSignTxResponse: + return processSignTX(msg.sign_tx_response()); + case SignerMessage::kWalletDeleted: + if (mainWindow_) { + const auto& itWallet = hdWallets_.find(msg.wallet_deleted()); + QMetaObject::invokeMethod(mainWindow_, [this, itWallet, rootId=msg.wallet_deleted()] { + bs::sync::WalletInfo wi; + if (itWallet == hdWallets_.end()) { + wi.ids.push_back(rootId); + } else { + wi = itWallet->second; + } + mainWindow_->onWalletDeleted(wi); + if (itWallet != hdWallets_.end()) { + hdWallets_.erase(itWallet); + } + }); + } + break; + default: break; + } + return true; +} + +bool QtGuiAdapter::processWallets(const Envelope &env) +{ + WalletsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case WalletsMessage::kLoading: + loadingComponents_.insert(env.sender->value()); + updateSplashProgress(); + break; + + case WalletsMessage::kWalletLoaded: { + const auto &wi = bs::sync::WalletInfo::fromCommonMsg(msg.wallet_loaded()); + processWalletLoaded(wi); + } + break; + + case WalletsMessage::kHdWallet: { + const auto &hdw = bs::sync::HDWalletData::fromCommonMessage(msg.hd_wallet()); + QMetaObject::invokeMethod(mainWindow_, [this, hdw] { + mainWindow_->onHDWalletDetails(hdw); + }); + } + break; + + case WalletsMessage::kWalletDeleted: { + const auto& wi = bs::sync::WalletInfo::fromCommonMsg(msg.wallet_deleted()); + QMetaObject::invokeMethod(mainWindow_, [this, wi]{ + mainWindow_->onWalletDeleted(wi); + }); + } + break; + + case WalletsMessage::kWalletAddresses: { + std::vector addresses; + for (const auto &addr : msg.wallet_addresses().addresses()) { + try { + addresses.push_back({ std::move(bs::Address::fromAddressString(addr.address())) + , addr.index(), addr.wallet_id() }); + } + catch (const std::exception &) {} + } + const auto& walletId = msg.wallet_addresses().wallet_id(); + auto itReq = needChangeAddrReqs_.find(env.responseId()); + if (itReq != needChangeAddrReqs_.end()) { + QMetaObject::invokeMethod(mainWindow_, [this, addresses, walletId]{ + mainWindow_->onChangeAddress(walletId, addresses.cbegin()->address); + }); + needChangeAddrReqs_.erase(itReq); + break; + } + QMetaObject::invokeMethod(mainWindow_, [this, addresses, walletId] { + mainWindow_->onAddresses(walletId, addresses); + }); + } + break; + + case WalletsMessage::kAddrComments: { + std::map comments; + for (const auto &addrComment : msg.addr_comments().comments()) { + try { + comments[bs::Address::fromAddressString(addrComment.address())] = addrComment.comment(); + } + catch (const std::exception &) {} + } + QMetaObject::invokeMethod(mainWindow_, [this, comments, walletId = msg.addr_comments().wallet_id()]{ + mainWindow_->onAddressComments(walletId, comments); + }); + } + break; + case WalletsMessage::kWalletData: + return processWalletData(env.responseId(), msg.wallet_data()); + case WalletsMessage::kWalletBalances: + return processWalletBalances(env, msg.wallet_balances()); + case WalletsMessage::kTxDetailsResponse: + return processTXDetails(env.responseId(), msg.tx_details_response()); + case WalletsMessage::kWalletsListResponse: + return processWalletsList(msg.wallets_list_response()); + case WalletsMessage::kUtxos: + return processUTXOs(msg.utxos()); + case WalletsMessage::kReservedUtxos: + return processReservedUTXOs(msg.reserved_utxos()); + case WalletsMessage::kWalletChanged: + if (walletsReady_) { + onNeedLedgerEntries({}); + } + break; + case WalletsMessage::kLedgerEntries: + return processLedgerEntries(msg.ledger_entries()); + default: break; + } + return true; +} + +bool QtGuiAdapter::processOnChainTrack(const Envelope &env) +{ + OnChainTrackMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case OnChainTrackMessage::kLoading: + loadingComponents_.insert(env.sender->value()); + updateSplashProgress(); + break; + default: break; + } + return true; +} + +bool QtGuiAdapter::processAssets(const bs::message::Envelope& env) +{ + AssetsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case AssetsMessage::kBalance: + return processBalance(msg.balance()); + default: break; + } + return true; +} + +void QtGuiAdapter::updateStates() +{ + if (!mainWindow_) { + return; + } + if (armoryState_ >= 0) { + mainWindow_->onArmoryStateChanged(armoryState_, blockNum_); + } + if (signerState_ >= 0) { + mainWindow_->onSignerStateChanged(signerState_, signerDetails_); + } + for (const auto &hdWallet : hdWallets_) { + mainWindow_->onHDWallet(hdWallet.second); + } + if (walletsReady_) { + mainWindow_->onWalletsReady(); + } +} + +void QtGuiAdapter::updateSplashProgress() +{ + std::lock_guard lock(mutex_); + if (!splashScreen_ || createdComponents_.empty()) { + return; + } +/* std::string l, c; + for (const auto &lc : loadingComponents_) { + l += std::to_string(lc) + " "; + } + for (const auto &cc : createdComponents_) { + c += std::to_string(cc) + " "; + } + logger_->debug("[{}] {}/{}", __func__, l, c);*/ + int percent = 100 * loadingComponents_.size() / createdComponents_.size(); + QMetaObject::invokeMethod(splashScreen_, [this, percent] { + splashScreen_->SetProgress(percent); + }); + if (percent >= 100) { + splashProgressCompleted(); + } +} + +void QtGuiAdapter::splashProgressCompleted() +{ + if (!splashScreen_) { + return; + } + loadingComponents_.clear(); + + QMetaObject::invokeMethod(splashScreen_, [this] { + mainWindow_->show(); + QTimer::singleShot(500, [this] { + if (splashScreen_) { + splashScreen_->hide(); + splashScreen_->deleteLater(); + splashScreen_ = nullptr; + } + }); + }); +} + +void QtGuiAdapter::requestInitialSettings() +{ + SettingsMessage msg; + auto msgReq = msg.mutable_get_request(); + auto setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_GUI_MainGeom); + setReq->set_type(SettingType_Rect); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_Initialized); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_GUI_MainTab); + setReq->set_type(SettingType_Int); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_ShowInfoWidget); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_AdvancedTXisDefault); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_CloseToTray); + setReq->set_type(SettingType_Bool); + + setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(SetIdx_Environment); + setReq->set_type(SettingType_Int); + + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::makeMainWinConnections() +{ + connect(mainWindow_, &bs::gui::qt::MainWindow::getSettings, this, &QtGuiAdapter::onGetSettings); + connect(mainWindow_, &bs::gui::qt::MainWindow::putSetting, this, &QtGuiAdapter::onPutSetting); + connect(mainWindow_, &bs::gui::qt::MainWindow::resetSettings, this, &QtGuiAdapter::onResetSettings); + connect(mainWindow_, &bs::gui::qt::MainWindow::resetSettingsToState, this, &QtGuiAdapter::onResetSettingsToState); + connect(mainWindow_, &bs::gui::qt::MainWindow::needSettingsState, this, &QtGuiAdapter::onNeedSettingsState); + connect(mainWindow_, &bs::gui::qt::MainWindow::needArmoryServers, this, &QtGuiAdapter::onNeedArmoryServers); + connect(mainWindow_, &bs::gui::qt::MainWindow::setArmoryServer, this, &QtGuiAdapter::onSetArmoryServer); + connect(mainWindow_, &bs::gui::qt::MainWindow::addArmoryServer, this, &QtGuiAdapter::onAddArmoryServer); + connect(mainWindow_, &bs::gui::qt::MainWindow::delArmoryServer, this, &QtGuiAdapter::onDelArmoryServer); + connect(mainWindow_, &bs::gui::qt::MainWindow::updArmoryServer, this, &QtGuiAdapter::onUpdArmoryServer); + connect(mainWindow_, &bs::gui::qt::MainWindow::needArmoryReconnect, this, &QtGuiAdapter::onNeedArmoryReconnect); + connect(mainWindow_, &bs::gui::qt::MainWindow::needSigners, this, &QtGuiAdapter::onNeedSigners); + connect(mainWindow_, &bs::gui::qt::MainWindow::setSigner, this, &QtGuiAdapter::onSetSigner); + connect(mainWindow_, &bs::gui::qt::MainWindow::needHDWalletDetails, this, &QtGuiAdapter::onNeedHDWalletDetails); + connect(mainWindow_, &bs::gui::qt::MainWindow::needWalletData, this, &QtGuiAdapter::onNeedWalletData); + connect(mainWindow_, &bs::gui::qt::MainWindow::needWalletBalances, this, &QtGuiAdapter::onNeedWalletBalances); + connect(mainWindow_, &bs::gui::qt::MainWindow::createExtAddress, this, &QtGuiAdapter::onCreateExtAddress); + connect(mainWindow_, &bs::gui::qt::MainWindow::needExtAddresses, this, &QtGuiAdapter::onNeedExtAddresses); + connect(mainWindow_, &bs::gui::qt::MainWindow::needIntAddresses, this, &QtGuiAdapter::onNeedIntAddresses); + connect(mainWindow_, &bs::gui::qt::MainWindow::needChangeAddress, this, &QtGuiAdapter::onNeedChangeAddress); + connect(mainWindow_, &bs::gui::qt::MainWindow::needUsedAddresses, this, &QtGuiAdapter::onNeedUsedAddresses); + connect(mainWindow_, &bs::gui::qt::MainWindow::needAddrComments, this, &QtGuiAdapter::onNeedAddrComments); + connect(mainWindow_, &bs::gui::qt::MainWindow::setAddrComment, this, &QtGuiAdapter::onSetAddrComment); + connect(mainWindow_, &bs::gui::qt::MainWindow::needLedgerEntries, this, &QtGuiAdapter::onNeedLedgerEntries); + connect(mainWindow_, &bs::gui::qt::MainWindow::needTXDetails, this, &QtGuiAdapter::onNeedTXDetails); + connect(mainWindow_, &bs::gui::qt::MainWindow::needAddressHistory, this, &QtGuiAdapter::onNeedAddressHistory); + connect(mainWindow_, &bs::gui::qt::MainWindow::needWalletsList, this, &QtGuiAdapter::onNeedWalletsList); + connect(mainWindow_, &bs::gui::qt::MainWindow::needFeeLevels, this, &QtGuiAdapter::onNeedFeeLevels); + connect(mainWindow_, &bs::gui::qt::MainWindow::needUTXOs, this, &QtGuiAdapter::onNeedUTXOs); + connect(mainWindow_, &bs::gui::qt::MainWindow::needSignTX, this, &QtGuiAdapter::onNeedSignTX); + connect(mainWindow_, &bs::gui::qt::MainWindow::needBroadcastZC, this, &QtGuiAdapter::onNeedBroadcastZC); + connect(mainWindow_, &bs::gui::qt::MainWindow::needSetTxComment, this, &QtGuiAdapter::onNeedSetTxComment); + connect(mainWindow_, &bs::gui::qt::MainWindow::needOpenBsConnection, this, &QtGuiAdapter::onNeedOpenBsConnection); + connect(mainWindow_, &bs::gui::qt::MainWindow::needCloseBsConnection, this, &QtGuiAdapter::onNeedCloseBsConnection); + connect(mainWindow_, &bs::gui::qt::MainWindow::needStartLogin, this, &QtGuiAdapter::onNeedStartLogin); + connect(mainWindow_, &bs::gui::qt::MainWindow::needCancelLogin, this, &QtGuiAdapter::onNeedCancelLogin); + connect(mainWindow_, &bs::gui::qt::MainWindow::needMatchingLogout, this, &QtGuiAdapter::onNeedMatchingLogout); + connect(mainWindow_, &bs::gui::qt::MainWindow::needMdConnection, this, &QtGuiAdapter::onNeedMdConnection); + connect(mainWindow_, &bs::gui::qt::MainWindow::needReserveUTXOs, this, &QtGuiAdapter::onNeedReserveUTXOs); + connect(mainWindow_, &bs::gui::qt::MainWindow::needUnreserveUTXOs, this, &QtGuiAdapter::onNeedUnreserveUTXOs); + connect(mainWindow_, &bs::gui::qt::MainWindow::needWalletDialog, this, &QtGuiAdapter::onNeedWalletDialog); +} + +void QtGuiAdapter::onGetSettings(const std::vector& settings) +{ + SettingsMessage msg; + auto msgReq = msg.mutable_get_request(); + for (const auto& setting : settings) { + auto setReq = msgReq->add_requests(); + setReq->set_source(SettingSource_Local); + setReq->set_index(static_cast(setting)); + } + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onPutSetting(ApplicationSettings::Setting idx, const QVariant &value) +{ + SettingsMessage msg; + auto msgReq = msg.mutable_put_request(); + auto setResp = msgReq->add_responses(); + auto setReq = setResp->mutable_request(); + setReq->set_source(SettingSource_Local); + setReq->set_index(static_cast(idx)); + setFromQVariant(value, setReq, setResp); + + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onResetSettings(const std::vector& settings) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_reset(); + for (const auto& setting : settings) { + auto msgReq = msgResp->add_requests(); + msgReq->set_index(static_cast(setting)); + msgReq->set_source(SettingSource_Local); + } + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onResetSettingsToState(const ApplicationSettings::State& state) +{ + SettingsMessage msg; + auto msgResp = msg.mutable_reset_to_state(); + for (const auto& st : state) { + auto setResp = msgResp->add_responses(); + auto setReq = setResp->mutable_request(); + setReq->set_source(SettingSource_Local); + setReq->set_index(static_cast(st.first)); + setFromQVariant(st.second, setReq, setResp); + } + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedSettingsState() +{ + SettingsMessage msg; + msg.mutable_state_get(); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedArmoryServers() +{ + SettingsMessage msg; + msg.mutable_armory_servers_get(); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onSetArmoryServer(int index) +{ + SettingsMessage msg; + msg.set_set_armory_server(index); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onAddArmoryServer(const ArmoryServer& server) +{ + SettingsMessage msg; + auto msgReq = msg.mutable_add_armory_server(); + msgReq->set_network_type((int)server.netType); + msgReq->set_server_name(server.name.toStdString()); + msgReq->set_server_address(server.armoryDBIp.toStdString()); + msgReq->set_server_port(std::to_string(server.armoryDBPort)); + msgReq->set_server_key(server.armoryDBKey.toStdString()); + msgReq->set_run_locally(server.runLocally); + msgReq->set_one_way_auth(server.oneWayAuth_); + msgReq->set_password(server.password.toBinStr()); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onDelArmoryServer(int index) +{ + SettingsMessage msg; + msg.set_del_armory_server(index); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onUpdArmoryServer(int index, const ArmoryServer& server) +{ + SettingsMessage msg; + auto msgReq = msg.mutable_upd_armory_server(); + msgReq->set_index(index); + auto msgSrv = msgReq->mutable_server(); + msgSrv->set_network_type((int)server.netType); + msgSrv->set_server_name(server.name.toStdString()); + msgSrv->set_server_address(server.armoryDBIp.toStdString()); + msgSrv->set_server_port(std::to_string(server.armoryDBPort)); + msgSrv->set_server_key(server.armoryDBKey.toStdString()); + msgSrv->set_run_locally(server.runLocally); + msgSrv->set_one_way_auth(server.oneWayAuth_); + msgSrv->set_password(server.password.toBinStr()); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::createWallet(bool primary) +{ + logger_->debug("[{}] primary: {}", __func__, primary); +} + +void QtGuiAdapter::onNeedHDWalletDetails(const std::string &walletId) +{ + WalletsMessage msg; + msg.set_hd_wallet_get(walletId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedWalletBalances(const std::string &walletId) +{ + logger_->debug("[{}] {}", __func__, walletId); + WalletsMessage msg; + msg.set_get_wallet_balances(walletId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedWalletData(const std::string& walletId) +{ + WalletsMessage msg; + msg.set_wallet_get(walletId); + const auto msgId = pushRequest(user_, userWallets_, msg.SerializeAsString()); + if (msgId) { + walletGetMap_[msgId] = walletId; + } +} + +void QtGuiAdapter::onCreateExtAddress(const std::string& walletId) +{ + WalletsMessage msg; + msg.set_create_ext_address(walletId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedExtAddresses(const std::string &walletId) +{ + logger_->debug("[{}] {}", __func__, walletId); + WalletsMessage msg; + msg.set_get_ext_addresses(walletId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedIntAddresses(const std::string &walletId) +{ + logger_->debug("[{}] {}", __func__, walletId); + WalletsMessage msg; + msg.set_get_int_addresses(walletId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedChangeAddress(const std::string& walletId) +{ + WalletsMessage msg; + msg.set_get_change_addr(walletId); + const auto msgId = pushRequest(user_, userWallets_, msg.SerializeAsString()); + needChangeAddrReqs_.insert(msgId); + logger_->debug("[{}] {} #{}", __func__, walletId, msgId); +} + +void QtGuiAdapter::onNeedUsedAddresses(const std::string &walletId) +{ + logger_->debug("[{}] {}", __func__, walletId); + WalletsMessage msg; + msg.set_get_used_addresses(walletId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedAddrComments(const std::string &walletId + , const std::vector &addrs) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_get_addr_comments(); + msgReq->set_wallet_id(walletId); + for (const auto &addr : addrs) { + auto addrReq = msgReq->add_addresses(); + addrReq->set_address(addr.display()); + } + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onSetAddrComment(const std::string &walletId, const bs::Address &addr + , const std::string &comment) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_set_addr_comments(); + msgReq->set_wallet_id(walletId); + auto msgComm = msgReq->add_comments(); + msgComm->set_address(addr.display()); + msgComm->set_comment(comment); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedLedgerEntries(const std::string &filter) +{ + WalletsMessage msg; + msg.set_get_ledger_entries(filter); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedTXDetails(const std::vector &txWallet + , bool useCache, const bs::Address &addr) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_tx_details_request(); + for (const auto &txw : txWallet) { + auto request = msgReq->add_requests(); + //logger_->debug("[{}] {}", __func__, txw.txHash.toHexStr()); + request->set_tx_hash(txw.txHash.toBinStr()); + request->set_wallet_id(txw.walletId); + request->set_value(txw.value); + } + if (!addr.empty()) { + msgReq->set_address(addr.display()); + } + msgReq->set_use_cache(useCache); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedAddressHistory(const bs::Address& addr) +{ + logger_->debug("[{}] {}", __func__, addr.display()); + ArmoryMessage msg; + msg.set_get_address_history(addr.display()); + pushRequest(user_, userBlockchain_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedWalletsList(UiUtils::WalletsTypes wt, const std::string &id) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_wallets_list_request(); + msgReq->set_id(id); + if (wt & UiUtils::WalletsTypes::WatchOnly) { + msgReq->set_watch_only(true); + } + if (wt & UiUtils::WalletsTypes::Full) { + msgReq->set_full(true); + } + if (wt & UiUtils::WalletsTypes::HardwareLegacy) { + msgReq->set_legacy(true); + msgReq->set_hardware(true); + } + if (wt & UiUtils::WalletsTypes::HardwareNativeSW) { + msgReq->set_native_sw(true); + msgReq->set_hardware(true); + } + if (wt & UiUtils::WalletsTypes::HardwareNestedSW) { + msgReq->set_nested_sw(true); + msgReq->set_hardware(true); + } + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedFeeLevels(const std::vector& levels) +{ + ArmoryMessage msg; + auto msgReq = msg.mutable_fee_levels_request(); + for (const auto& level : levels) { + msgReq->add_levels(level); + } + pushRequest(user_, userBlockchain_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedUTXOs(const std::string& id, const std::string& walletId, bool confOnly, bool swOnly) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_get_utxos(); + msgReq->set_id(id); + msgReq->set_wallet_id(walletId); + msgReq->set_confirmed_only(confOnly); + msgReq->set_segwit_only(swOnly); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedSignTX(const std::string& id + , const bs::core::wallet::TXSignRequest& txReq, bool keepDupRecips + , SignContainer::TXSignMode mode, const SecureBinaryData& passphrase) +{ + SignerMessage msg; + auto msgReq = msg.mutable_sign_tx_request(); + msgReq->set_id(id); + *msgReq->mutable_tx_request() = bs::signer::coreTxRequestToPb(txReq, keepDupRecips); + msgReq->set_sign_mode((int)mode); + msgReq->set_keep_dup_recips(keepDupRecips); + msgReq->set_passphrase(passphrase.toBinStr()); + pushRequest(user_, userSigner_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedBroadcastZC(const std::string& id, const BinaryData& tx) +{ + ArmoryMessage msg; + auto msgReq = msg.mutable_tx_push(); + msgReq->set_push_id(id); + auto msgTx = msgReq->add_txs_to_push(); + msgTx->set_tx(tx.toBinStr()); + //not adding TX hashes atm + pushRequest(user_, userBlockchain_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedArmoryReconnect() +{ + ArmoryMessage msg; + msg.mutable_reconnect(); + pushRequest(user_, userBlockchain_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedSigners() +{ + SettingsMessage msg; + msg.mutable_signer_servers_get(); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onSetSigner(int index) +{ + SettingsMessage msg; + msg.set_set_signer_server(index); + pushRequest(user_, userSettings_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedSetTxComment(const std::string& walletId, const BinaryData& txHash, const std::string& comment) +{ + logger_->debug("[{}] {}: {}", __func__, txHash.toHexStr(true), comment); + WalletsMessage msg; + auto msgReq = msg.mutable_set_tx_comment(); + msgReq->set_wallet_id(walletId); + msgReq->set_tx_hash(txHash.toBinStr()); + msgReq->set_comment(comment); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedOpenBsConnection() +{ + BsServerMessage msg; + msg.mutable_open_connection(); + pushRequest(user_, userBS_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedCloseBsConnection() +{ + BsServerMessage msg; + msg.mutable_close_connection(); + pushRequest(user_, userBS_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedStartLogin(const std::string& login) +{ + BsServerMessage msg; + msg.set_start_login(login); + pushRequest(user_, userBS_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedCancelLogin() +{ + BsServerMessage msg; + msg.mutable_cancel_last_login(); + pushRequest(user_, userBS_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedMatchingLogout() +{ + MatchingMessage msg; + msg.mutable_logout(); + pushRequest(user_, userMatch_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedMdConnection(ApplicationSettings::EnvConfiguration ec) +{ + MktDataMessage msg; + msg.set_start_connection((int)ec); + pushRequest(user_, userMD_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedReserveUTXOs(const std::string& reserveId + , const std::string& subId, uint64_t amount, bool withZC + , const std::vector& utxos) +{ + logger_->debug("[{}] {}/{} {}", __func__, reserveId, subId, amount); + WalletsMessage msg; + auto msgReq = msg.mutable_reserve_utxos(); + msgReq->set_id(reserveId); + msgReq->set_sub_id(subId); + msgReq->set_amount(amount); + msgReq->set_use_zc(withZC); + for (const auto& utxo : utxos) { + msgReq->add_utxos(utxo.serialize().toBinStr()); + } + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedUnreserveUTXOs(const std::string& reserveId + , const std::string& subId) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_unreserve_utxos(); + msgReq->set_id(reserveId); + msgReq->set_sub_id(subId); + pushRequest(user_, userWallets_, msg.SerializeAsString()); +} + +void QtGuiAdapter::onNeedWalletDialog(bs::signer::ui::GeneralDialogType dlgType + , const std::string& rootId) +{ + switch (dlgType) { + case bs::signer::ui::GeneralDialogType::CreateWallet: + if (mainWindow_) { + QMetaObject::invokeMethod(mainWindow_, [this, rootId] { + const auto& seedData = mainWindow_->getWalletSeed(rootId); + if (!seedData.empty()) { + SignerMessage msg; + auto msgReq = msg.mutable_create_wallet(); + msgReq->set_name(seedData.name); + msgReq->set_description(seedData.description); + msgReq->set_xpriv_key(seedData.xpriv); + msgReq->set_seed(seedData.seed.toBinStr()); + msgReq->set_password(seedData.password.toBinStr()); + pushRequest(user_, userSigner_, msg.SerializeAsString()); + } + }); + } + break; + case bs::signer::ui::GeneralDialogType::ImportWallet: + if (mainWindow_) { + QMetaObject::invokeMethod(mainWindow_, [this, rootId] { + const auto& seedData = mainWindow_->importWallet(rootId); + if (!seedData.empty()) { + SignerMessage msg; + auto msgReq = msg.mutable_import_wallet(); + msgReq->set_name(seedData.name); + msgReq->set_description(seedData.description); + msgReq->set_xpriv_key(seedData.xpriv); + msgReq->set_seed(seedData.seed.toBinStr()); + msgReq->set_password(seedData.password.toBinStr()); + pushRequest(user_, userSigner_, msg.SerializeAsString()); + } + }); + } + break; + case bs::signer::ui::GeneralDialogType::DeleteWallet: + if (mainWindow_) { + const auto& itWallet = hdWallets_.find(rootId); + const std::string walletName = (itWallet == hdWallets_.end()) ? "" : itWallet->second.name; + QMetaObject::invokeMethod(mainWindow_, [this, rootId, walletName] { + if (mainWindow_->deleteWallet(rootId, walletName)) { + SignerMessage msg; + msg.set_delete_wallet(rootId); + pushRequest(user_, userSigner_, msg.SerializeAsString()); + } + }); + } + break; + default: + logger_->debug("[{}] {} ({})", __func__, (int)dlgType, rootId); + break; + } +} + +void QtGuiAdapter::processWalletLoaded(const bs::sync::WalletInfo &wi) +{ + hdWallets_[*wi.ids.cbegin()] = wi; + if (mainWindow_) { + QMetaObject::invokeMethod(mainWindow_, [this, wi] { + mainWindow_->onHDWallet(wi); + }); + } +} + +bool QtGuiAdapter::processWalletData(uint64_t msgId + , const WalletsMessage_WalletData& response) +{ + const auto& itWallet = walletGetMap_.find(msgId); + if (itWallet == walletGetMap_.end()) { + return true; + } + const auto& walletId = itWallet->second; + const auto& walletData = bs::sync::WalletData::fromCommonMessage(response); + if (QMetaObject::invokeMethod(mainWindow_, [this, walletId, walletData] { + mainWindow_->onWalletData(walletId, walletData); + })) { + walletGetMap_.erase(itWallet); + return true; + } + return false; +} + +bool QtGuiAdapter::processWalletBalances(const bs::message::Envelope & + , const WalletsMessage_WalletBalances &response) +{ + bs::sync::WalletBalanceData wbd; + wbd.id = response.wallet_id(); + wbd.balTotal = response.total_balance(); + wbd.balSpendable = response.spendable_balance(); + wbd.balUnconfirmed = response.unconfirmed_balance(); + wbd.nbAddresses = response.nb_addresses(); + for (const auto &addrBal : response.address_balances()) { + wbd.addrBalances.push_back({ BinaryData::fromString(addrBal.address()) + , addrBal.tx_count(), (int64_t)addrBal.total_balance(), (int64_t)addrBal.spendable_balance() + , (int64_t)addrBal.unconfirmed_balance() }); + } + return QMetaObject::invokeMethod(mainWindow_, [this, wbd] { + mainWindow_->onWalletBalance(wbd); + }); +} + +bool QtGuiAdapter::processTXDetails(uint64_t msgId, const WalletsMessage_TXDetailsResponse &response) +{ + std::vector txDetails; + for (const auto &resp : response.responses()) { + bs::sync::TXWalletDetails txDet{ BinaryData::fromString(resp.tx_hash()), resp.wallet_id() + , resp.wallet_name(), static_cast(resp.wallet_type()) + , resp.wallet_symbol(), static_cast(resp.direction()) + , resp.comment(), resp.valid(), resp.amount() }; + if (!response.error_msg().empty()) { + txDet.comment = response.error_msg(); + } + + const auto &ownTxHash = BinaryData::fromString(resp.tx_hash()); + try { + if (!resp.tx().empty()) { + Tx tx(BinaryData::fromString(resp.tx())); + if (tx.isInitialized()) { + txDet.tx = std::move(tx); + } + } + } catch (const std::exception &e) { + logger_->warn("[QtGuiAdapter::processTXDetails] TX deser error: {}", e.what()); + } + for (const auto &addrStr : resp.out_addresses()) { + try { + txDet.outAddresses.push_back(std::move(bs::Address::fromAddressString(addrStr))); + } catch (const std::exception &e) { + logger_->warn("[QtGuiAdapter::processTXDetails] out deser error: {}", e.what()); + } + } + for (const auto &inAddr : resp.input_addresses()) { + try { + txDet.inputAddresses.push_back({ bs::Address::fromAddressString(inAddr.address()) + , inAddr.value(), inAddr.value_string(), inAddr.wallet_name() + , static_cast(inAddr.script_type()) + , BinaryData::fromString(inAddr.out_hash()), inAddr.out_index() }); + } catch (const std::exception &e) { + logger_->warn("[QtGuiAdapter::processTXDetails] input deser error: {}", e.what()); + } + } + for (const auto &outAddr : resp.output_addresses()) { + try { + txDet.outputAddresses.push_back({ bs::Address::fromAddressString(outAddr.address()) + , outAddr.value(), outAddr.value_string(), outAddr.wallet_name() + , static_cast(outAddr.script_type()) + , BinaryData::fromString(outAddr.out_hash()), outAddr.out_index() }); + } catch (const std::exception &e) { // OP_RETURN data for valueStr + txDet.outputAddresses.push_back({ bs::Address{} + , outAddr.value(), outAddr.address(), outAddr.wallet_name() + , static_cast(outAddr.script_type()), ownTxHash + , outAddr.out_index() }); + } + } + try { + txDet.changeAddress = { bs::Address::fromAddressString(resp.change_address().address()) + , resp.change_address().value(), resp.change_address().value_string() + , resp.change_address().wallet_name() + , static_cast(resp.change_address().script_type()) + , BinaryData::fromString(resp.change_address().out_hash()) + , resp.change_address().out_index() }; + } + catch (const std::exception &) {} + txDetails.emplace_back(std::move(txDet)); + } + if (!response.responses_size() && !response.error_msg().empty()) { + bs::sync::TXWalletDetails txDet; + txDet.comment = response.error_msg(); + txDetails.emplace_back(std::move(txDet)); + } + const auto& itZC = newZCs_.find(msgId); + if (itZC != newZCs_.end()) { + newZCs_.erase(itZC); + return QMetaObject::invokeMethod(mainWindow_, [this, txDetails] { + mainWindow_->onNewZCs(txDetails); + }); + } + return QMetaObject::invokeMethod(mainWindow_, [this, txDetails] { + mainWindow_->onTXDetails(txDetails); + }); +} + +bool QtGuiAdapter::processLedgerEntries(const LedgerEntries &response) +{ + std::vector entries; + for (const auto &entry : response.entries()) { + bs::TXEntry txEntry; + txEntry.txHash = BinaryData::fromString(entry.tx_hash()); + txEntry.value = entry.value(); + txEntry.blockNum = entry.block_num(); + txEntry.txTime = entry.tx_time(); + txEntry.isRBF = entry.rbf(); + txEntry.isChainedZC = entry.chained_zc(); + txEntry.nbConf = entry.nb_conf(); + for (const auto &walletId : entry.wallet_ids()) { + txEntry.walletIds.insert(walletId); + } + for (const auto &addrStr : entry.addresses()) { + try { + const auto &addr = bs::Address::fromAddressString(addrStr); + txEntry.addresses.push_back(addr); + } + catch (const std::exception &) {} + } + entries.push_back(std::move(txEntry)); + } + return QMetaObject::invokeMethod(mainWindow_, [this, entries, filter=response.filter() + , totPages=response.total_pages(), curPage=response.cur_page() + , curBlock=response.cur_block()] { + mainWindow_->onLedgerEntries(filter, totPages, curPage, curBlock, entries); + }); +} + + +bool QtGuiAdapter::processAddressHist(const ArmoryMessage_AddressHistory& response) +{ + bs::Address addr; + try { + addr = std::move(bs::Address::fromAddressString(response.address())); + } + catch (const std::exception& e) { + logger_->error("[{}] invalid address: {}", __func__, e.what()); + return true; + } + std::vector entries; + for (const auto& entry : response.entries()) { + bs::TXEntry txEntry; + txEntry.txHash = BinaryData::fromString(entry.tx_hash()); + txEntry.value = entry.value(); + txEntry.blockNum = entry.block_num(); + txEntry.txTime = entry.tx_time(); + txEntry.isRBF = entry.rbf(); + txEntry.isChainedZC = entry.chained_zc(); + txEntry.nbConf = entry.nb_conf(); + for (const auto& walletId : entry.wallet_ids()) { + txEntry.walletIds.insert(walletId); + } + for (const auto& addrStr : entry.addresses()) { + try { + const auto& addr = bs::Address::fromAddressString(addrStr); + txEntry.addresses.push_back(addr); + } + catch (const std::exception&) {} + } + entries.push_back(std::move(txEntry)); + } + return QMetaObject::invokeMethod(mainWindow_, [this, entries, addr, curBlock = response.cur_block()] { + mainWindow_->onAddressHistory(addr, curBlock, entries); + }); +} + +bool QtGuiAdapter::processFeeLevels(const ArmoryMessage_FeeLevelsResponse& response) +{ + std::map feeLevels; + for (const auto& pair : response.fee_levels()) { + feeLevels[pair.level()] = pair.fee(); + } + return QMetaObject::invokeMethod(mainWindow_, [this, feeLevels]{ + mainWindow_->onFeeLevels(feeLevels); + }); +} + +bool QtGuiAdapter::processWalletsList(const WalletsMessage_WalletsListResponse& response) +{ + std::vector wallets; + for (const auto& wallet : response.wallets()) { + wallets.push_back(bs::sync::HDWalletData::fromCommonMessage(wallet)); + } + + return QMetaObject::invokeMethod(mainWindow_, [this, wallets, id = response.id()]{ + mainWindow_->onWalletsList(id, wallets); + }); +} + +bool QtGuiAdapter::processUTXOs(const WalletsMessage_UtxoListResponse& response) +{ + std::vector utxos; + for (const auto& serUtxo : response.utxos()) { + UTXO utxo; + utxo.unserialize(BinaryData::fromString(serUtxo)); + utxos.push_back(std::move(utxo)); + } + return QMetaObject::invokeMethod(mainWindow_, [this, utxos, response]{ + mainWindow_->onUTXOs(response.id(), response.wallet_id(), utxos); + }); +} + +bool QtGuiAdapter::processSignTX(const BlockSettle::Common::SignerMessage_SignTxResponse& response) +{ + return QMetaObject::invokeMethod(mainWindow_, [this, response] { + mainWindow_->onSignedTX(response.id(), BinaryData::fromString(response.signed_tx()) + , static_cast(response.error_code())); + }); +} + +bool QtGuiAdapter::processZC(const BlockSettle::Common::ArmoryMessage_ZCReceived& zcs) +{ + WalletsMessage msg; + auto msgReq = msg.mutable_tx_details_request(); + for (const auto& zcEntry : zcs.tx_entries()) { + auto txReq = msgReq->add_requests(); + txReq->set_tx_hash(zcEntry.tx_hash()); + if (zcEntry.wallet_ids_size() > 0) { + txReq->set_wallet_id(zcEntry.wallet_ids(0)); + } + txReq->set_value(zcEntry.value()); + } + const auto msgId = pushRequest(user_, userWallets_, msg.SerializeAsString()); + if (!msgId) { + return false; + } + newZCs_.insert(msgId); + return true; +} + +bool QtGuiAdapter::processZCInvalidated(const ArmoryMessage_ZCInvalidated& zcInv) +{ + std::vector txHashes; + for (const auto& hashStr : zcInv.tx_hashes()) { + txHashes.push_back(BinaryData::fromString(hashStr)); + } + return QMetaObject::invokeMethod(mainWindow_, [this, txHashes] { + mainWindow_->onZCsInvalidated(txHashes); + }); +} + +bool QtGuiAdapter::processBsServer(const bs::message::Envelope& env) +{ + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case BsServerMessage::kStartLoginResult: + return processStartLogin(msg.start_login_result()); + case BsServerMessage::kLoginResult: + return processLogin(msg.login_result()); + default: break; + } + return true; +} + +bool QtGuiAdapter::processStartLogin(const BsServerMessage_StartLoginResult& response) +{ + return QMetaObject::invokeMethod(mainWindow_, [this, response] { + mainWindow_->onLoginStarted(response.login(), response.success() + , response.error_text()); + }); +} + +bool QtGuiAdapter::processLogin(const BsServerMessage_LoginResult& response) +{ +#if 0 + result.login = response.login(); + result.status = static_cast(response.status()); + result.userType = static_cast(response.user_type()); + result.errorMsg = response.error_text(); + result.celerLogin = response.celer_login(); + result.chatTokenData = BinaryData::fromString(response.chat_token()); + result.chatTokenSign = BinaryData::fromString(response.chat_token_signature()); + result.bootstrapDataSigned = response.bootstrap_signed_data(); + result.enabled = response.enabled(); + result.feeRatePb = response.fee_rate(); + result.tradeSettings.xbtTier1Limit = response.trade_settings().xbt_tier1_limit(); + result.tradeSettings.xbtPriceBand = response.trade_settings().xbt_price_band(); + result.tradeSettings.authRequiredSettledTrades = response.trade_settings().auth_reqd_settl_trades(); + result.tradeSettings.authSubmitAddressLimit = response.trade_settings().auth_submit_addr_limit(); + result.tradeSettings.dealerAuthSubmitAddressLimit = response.trade_settings().dealer_auth_submit_addr_limit(); + + return QMetaObject::invokeMethod(mainWindow_, [this, result] { + mainWindow_->onLoggedIn(result); + }); +#else + return true; +#endif 0 +} + +bool QtGuiAdapter::processMatching(const bs::message::Envelope& env) +{ + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MatchingMessage::kLoggedIn: +#if 0 + return QMetaObject::invokeMethod(mainWindow_, [this, response=msg.logged_in()] { + mainWindow_->onMatchingLogin(response.user_name() + , static_cast(response.user_type()), response.user_id()); + }); +#endif + case MatchingMessage::kLoggedOut: + return QMetaObject::invokeMethod(mainWindow_, [this] { + //mainWindow_->onMatchingLogout(); + }); +/* case MatchingMessage::kQuoteNotif: + return processQuoteNotif(msg.quote_notif());*/ + default: break; + } + return true; +} + +bool QtGuiAdapter::processMktData(const bs::message::Envelope& env) +{ + MktDataMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MktDataMessage::kConnected: + //return QMetaObject::invokeMethod(mainWindow_, [this] { mainWindow_->onMDConnected(); }); + case MktDataMessage::kDisconnected: + mdInstrumentsReceived_ = false; + //return QMetaObject::invokeMethod(mainWindow_, [this] { mainWindow_->onMDDisconnected(); }); + return true; + case MktDataMessage::kNewSecurity: + return processSecurity(msg.new_security().name(), msg.new_security().asset_type()); + case MktDataMessage::kAllInstrumentsReceived: + mdInstrumentsReceived_ = true; + return true; // sendPooledOrdersUpdate(); + case MktDataMessage::kPriceUpdate: + return processMdUpdate(msg.price_update()); + default: break; + } + return true; +} + +bool QtGuiAdapter::processSecurity(const std::string& name, int assetType) +{ + const auto &at = static_cast(assetType); + assetTypes_[name] = at; + return true; +} + +bool QtGuiAdapter::processMdUpdate(const MktDataMessage_Prices& msg) +{ + return QMetaObject::invokeMethod(mainWindow_, [this, msg] { + const bs::network::MDFields fields{ + { bs::network::MDField::Type::PriceBid, msg.bid() }, + { bs::network::MDField::Type::PriceOffer, msg.ask() }, + { bs::network::MDField::Type::PriceLast, msg.last() }, + { bs::network::MDField::Type::DailyVolume, msg.volume() }, + { bs::network::MDField::Type::MDTimestamp, (double)msg.timestamp() } + }; + mainWindow_->onMDUpdated(static_cast(msg.security().asset_type()) + , QString::fromStdString(msg.security().name()), fields); + }); +} + +bool QtGuiAdapter::processBalance(const AssetsMessage_Balance& bal) +{ + return QMetaObject::invokeMethod(mainWindow_, [this, bal] { + mainWindow_->onBalance(bal.currency(), bal.value()); + }); +} + +bool QtGuiAdapter::processReservedUTXOs(const WalletsMessage_ReservedUTXOs& response) +{ + std::vector utxos; + for (const auto& utxoSer : response.utxos()) { + UTXO utxo; + utxo.unserialize(BinaryData::fromString(utxoSer)); + utxos.push_back(std::move(utxo)); + } + return QMetaObject::invokeMethod(mainWindow_, [this, response, utxos] { + mainWindow_->onReservedUTXOs(response.id(), response.sub_id(), utxos); + }); +} + +#include "QtGuiAdapter.moc" diff --git a/GUI/QtWidgets/QtGuiAdapter.h b/GUI/QtWidgets/QtGuiAdapter.h new file mode 100644 index 000000000..9ac8fd566 --- /dev/null +++ b/GUI/QtWidgets/QtGuiAdapter.h @@ -0,0 +1,212 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef QT_GUI_ADAPTER_H +#define QT_GUI_ADAPTER_H + +#include +#include +#include "Address.h" +#include "ApiAdapter.h" +#include "Wallets/SignContainer.h" +#include "ThreadSafeClasses.h" +#include "UiUtils.h" + +namespace bs { + namespace gui { + namespace qt { + class MainWindow; + } + } +} +namespace BlockSettle { + namespace Common { + class ArmoryMessage_AddressHistory; + class ArmoryMessage_FeeLevelsResponse; + class ArmoryMessage_ZCInvalidated; + class ArmoryMessage_ZCReceived; + class LedgerEntries; + class OnChainTrackMessage_AuthAddresses; + class OnChainTrackMessage_AuthState; + class SignerMessage_SignTxResponse; + class WalletsMessage_AuthKey; + class WalletsMessage_ReservedUTXOs; + class WalletsMessage_TXDetailsResponse; + class WalletsMessage_UtxoListResponse; + class WalletsMessage_WalletBalances; + class WalletsMessage_WalletData; + class WalletsMessage_WalletsListResponse; + } + namespace Terminal { + class AssetsMessage_Balance; + class AssetsMessage_SubmittedAuthAddresses; + class BsServerMessage_LoginResult; + class BsServerMessage_Orders; + class BsServerMessage_StartLoginResult; + class MatchingMessage_LoggedIn; + class Quote; + class QuoteCancelled; + class IncomingRFQ; + class MatchingMessage_Order; + class MktDataMessage_Prices; + class SettlementMessage_FailedSettlement; + class SettlementMessage_MatchedQuote; + class SettlementMessage_PendingSettlement; + class SettlementMessage_SettlementIds; + class SettingsMessage_ArmoryServers; + class SettingsMessage_SettingsResponse; + class SettingsMessage_SignerServers; + } +} +class BSTerminalSplashScreen; +class GuiThread; + + +class QtGuiAdapter : public QObject, public ApiBusAdapter, public bs::MainLoopRuner +{ + Q_OBJECT + friend class GuiThread; +public: + QtGuiAdapter(const std::shared_ptr &); + ~QtGuiAdapter() override; + + bool process(const bs::message::Envelope &) override; + bool processBroadcast(const bs::message::Envelope&) override; + + Users supportedReceivers() const override { return { user_ }; } + std::string name() const override { return "QtGUI"; } + + void run(int &argc, char **argv) override; + +private: + bool processSettings(const bs::message::Envelope &); + bool processSettingsGetResponse(const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + bool processSettingsState(const BlockSettle::Terminal::SettingsMessage_SettingsResponse&); + bool processArmoryServers(const BlockSettle::Terminal::SettingsMessage_ArmoryServers&); + bool processAdminMessage(const bs::message::Envelope &); + bool processBlockchain(const bs::message::Envelope &); + bool processSigner(const bs::message::Envelope &); + bool processWallets(const bs::message::Envelope &); + bool processOnChainTrack(const bs::message::Envelope &); + bool processAssets(const bs::message::Envelope&); + + void requestInitialSettings(); + void updateSplashProgress(); + void splashProgressCompleted(); + void updateStates(); + + void createWallet(bool primary); + void makeMainWinConnections(); + + void processWalletLoaded(const bs::sync::WalletInfo &); + bool processWalletData(const uint64_t msgId + , const BlockSettle::Common::WalletsMessage_WalletData&); + bool processWalletBalances(const bs::message::Envelope & + , const BlockSettle::Common::WalletsMessage_WalletBalances &); + bool processTXDetails(uint64_t msgId, const BlockSettle::Common::WalletsMessage_TXDetailsResponse &); + bool processLedgerEntries(const BlockSettle::Common::LedgerEntries &); + bool processAddressHist(const BlockSettle::Common::ArmoryMessage_AddressHistory&); + bool processFeeLevels(const BlockSettle::Common::ArmoryMessage_FeeLevelsResponse&); + bool processWalletsList(const BlockSettle::Common::WalletsMessage_WalletsListResponse&); + bool processUTXOs(const BlockSettle::Common::WalletsMessage_UtxoListResponse&); + bool processSignTX(const BlockSettle::Common::SignerMessage_SignTxResponse&); + bool processZC(const BlockSettle::Common::ArmoryMessage_ZCReceived&); + bool processZCInvalidated(const BlockSettle::Common::ArmoryMessage_ZCInvalidated&); + + bool processBsServer(const bs::message::Envelope&); + bool processStartLogin(const BlockSettle::Terminal::BsServerMessage_StartLoginResult&); + bool processLogin(const BlockSettle::Terminal::BsServerMessage_LoginResult&); + + bool processMatching(const bs::message::Envelope&); + bool processMktData(const bs::message::Envelope&); + bool processSecurity(const std::string&, int); + bool processMdUpdate(const BlockSettle::Terminal::MktDataMessage_Prices &); + bool processBalance(const BlockSettle::Terminal::AssetsMessage_Balance&); + bool processReservedUTXOs(const BlockSettle::Common::WalletsMessage_ReservedUTXOs&); + +private slots: + void onGetSettings(const std::vector&); + void onPutSetting(ApplicationSettings::Setting, const QVariant &value); + void onResetSettings(const std::vector&); + void onResetSettingsToState(const ApplicationSettings::State&); + void onNeedSettingsState(); + void onNeedArmoryServers(); + void onSetArmoryServer(int); + void onAddArmoryServer(const ArmoryServer&); + void onDelArmoryServer(int); + void onUpdArmoryServer(int, const ArmoryServer&); + void onNeedArmoryReconnect(); + void onNeedSigners(); + void onSetSigner(int); + void onNeedHDWalletDetails(const std::string &walletId); + void onNeedWalletBalances(const std::string &walletId); + void onNeedWalletData(const std::string& walletId); + void onCreateExtAddress(const std::string& walletId); + void onNeedExtAddresses(const std::string &walletId); + void onNeedIntAddresses(const std::string &walletId); + void onNeedChangeAddress(const std::string& walletId); + void onNeedUsedAddresses(const std::string &walletId); + void onNeedAddrComments(const std::string &walletId, const std::vector &); + void onSetAddrComment(const std::string &walletId, const bs::Address & + , const std::string &comment); + void onNeedLedgerEntries(const std::string &filter); + void onNeedTXDetails(const std::vector &, bool useCache + , const bs::Address &); + void onNeedAddressHistory(const bs::Address&); + void onNeedWalletsList(UiUtils::WalletsTypes, const std::string &id); + void onNeedFeeLevels(const std::vector&); + void onNeedUTXOs(const std::string& id, const std::string& walletId + , bool confOnly, bool swOnly); + void onNeedSignTX(const std::string& id, const bs::core::wallet::TXSignRequest& + , bool keepDupRecips, SignContainer::TXSignMode, const SecureBinaryData& passphrase); + void onNeedBroadcastZC(const std::string& id, const BinaryData&); + void onNeedSetTxComment(const std::string& walletId, const BinaryData& txHash + , const std::string& comment); + void onNeedOpenBsConnection(); + void onNeedCloseBsConnection(); + void onNeedStartLogin(const std::string& login); + void onNeedCancelLogin(); + void onNeedMatchingLogout(); + void onNeedMdConnection(ApplicationSettings::EnvConfiguration); + void onNeedReserveUTXOs(const std::string& reserveId, const std::string& subId + , uint64_t amount, bool withZC = false, const std::vector& utxos = {}); + void onNeedUnreserveUTXOs(const std::string& reserveId, const std::string& subId); + void onNeedWalletDialog(bs::signer::ui::GeneralDialogType, const std::string& rootId); + +private: + std::shared_ptr logger_; + std::shared_ptr userSettings_, userWallets_; + std::shared_ptr userBlockchain_, userSigner_; + std::shared_ptr userBS_, userMatch_, userSettl_; + std::shared_ptr userMD_, userTrk_; + bs::gui::qt::MainWindow * mainWindow_{ nullptr }; + BSTerminalSplashScreen * splashScreen_{ nullptr }; + + std::recursive_mutex mutex_; + std::set createdComponents_; + std::set loadingComponents_; + int armoryState_{ -1 }; + uint32_t blockNum_{ 0 }; + int signerState_{ -1 }; + std::string signerDetails_; + bool walletsReady_{ false }; + + std::map walletGetMap_; + std::unordered_map hdWallets_; + std::set newZCs_; + + std::unordered_map assetTypes_; + bool mdInstrumentsReceived_{ false }; + + std::set needChangeAddrReqs_; +}; + + +#endif // QT_GUI_ADAPTER_H diff --git a/Scripts/RFQBot_LMAX.qml b/Scripts/RFQBot_LMAX.qml index 0aabf590c..c2fac2481 100644 --- a/Scripts/RFQBot_LMAX.qml +++ b/Scripts/RFQBot_LMAX.qml @@ -1,311 +1,311 @@ -/* - -*********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB -* Distributed under the GNU Affero General Public License (AGPL v3) -* See LICENSE or http://www.gnu.org/licenses/agpl.html -* -********************************************************************************** - -*/ -import bs.terminal 1.0 -BSQuoteReqReply { - //------------------------INTERFACE--------------------------- - property var firstShiftFromIndicPrice: 2 // in percent - - property var shiftLimitFromIndicPrice: 0.2 // in percent - - //------------------------------------------------------------ - - - //-----------------------BRIEF EXPLANATION------------------- - /* - On the first call of either onIndicBidChanged or onIndicBidChanged the script counts the initialPrice, check the limits and sends it to server on succeed. - Notice, that script won't react on bid or ask changing after that. - Next, on each onBestPriceChanged call script counts new price depening on direction() call that also counts quote or base currency was used. - If the price satisfies the limits - it's sent to server. - checkPrice() - checks whether the price lays between user limits - checkBalance() - checks whether the user have enough balance for the quantity of products. - */ - - property var initialPrice: 0.0 - property var prcIncrement: 0.005 // in percent - property var priceIncrement: 0.01 - property var finalPrice: 0.0 - property var hedgePrice: 0.0 - property var hedgeAllowed: true - property var settled: false - - onStarted: { // serve only fixed CC quotes here - if (!isCC()) { - var subs = '{"symbol":"' + security + '"}' - sendExtConn('LMAX', 'subscribe_prices', subs) - return - } - if (direction() !== 1) return - if (security == "LOL/XBT") { - sendPrice(0.0001) - } - else if (security == "ARM/XBT") { - sendPrice(0.001) - } - } +/* + +*********************************************************************************** +* Copyright (C) 2019 - 2020, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +import bs.terminal 1.0 +BSQuoteReqReply { + //------------------------INTERFACE--------------------------- + property var firstShiftFromIndicPrice: 2 // in percent - + property var shiftLimitFromIndicPrice: 0.2 // in percent - + //------------------------------------------------------------ + + + //-----------------------BRIEF EXPLANATION------------------- + /* + On the first call of either onIndicBidChanged or onIndicBidChanged the script counts the initialPrice, check the limits and sends it to server on succeed. + Notice, that script won't react on bid or ask changing after that. + Next, on each onBestPriceChanged call script counts new price depening on direction() call that also counts quote or base currency was used. + If the price satisfies the limits - it's sent to server. + checkPrice() - checks whether the price lays between user limits + checkBalance() - checks whether the user have enough balance for the quantity of products. + */ + + property var initialPrice: 0.0 + property var prcIncrement: 0.005 // in percent + property var priceIncrement: 0.01 + property var finalPrice: 0.0 + property var hedgePrice: 0.0 + property var hedgeAllowed: true + property var settled: false + + onStarted: { // serve only fixed CC quotes here + if (!isCC()) { + var subs = '{"symbol":"' + security + '"}' + sendExtConn('LMAX', 'subscribe_prices', subs) + return + } + if (direction() !== 1) return + if (security == "LOL/XBT") { + sendPrice(0.0001) + } + else if (security == "ARM/XBT") { + sendPrice(0.001) + } + } onCancelled: { settled = true //todo: stop } - - onBestPriceChanged: { - if (!hedgeAllowed || settled) return - log('new best price: ' + bestPrice) - if (bestPrice === finalPrice) return // our quote - - if (hedgePrice) { - if (direction() === 1) { - if (bestPrice > hedgePrice) { - sendPrice(hedgePrice) - } - else { - log("give up - best price too low: " + bestPrice + " vs " + hedgePrice) - } - } - else { - if (bestPrice < hedgePrice) { - sendPrice(hedgePrice) - } - else { - log("give up - best price too high: " + bestPrice + " vs " + hedgePrice) - } - } - } - } - - onExtDataReceived: { -// log('from: ' + from + ', type: ' + type + ', msg:\n' + msg) - if (from === 'LMAX') { - var msgObj = JSON.parse(msg) - if (type === 'mdprices') { - onLMAXprices(msgObj) - } - else if (type === 'order_intent') { - onLMAXintent(msgObj) - } - } - } - - onSettled: { - settled = true - log(security + ' settled at ' + finalPrice) - sendHedgeOrder(hedgePrice) - - var subs = '{"symbol":"' + security + '"}' - sendExtConn('LMAX', 'unsubscribe_prices', subs) - } - - function onLMAXprices(msgObj) - { - for (var i in msgObj.prices) { - var priceObj = msgObj.prices[i] - if (priceObj.symbol === security) { - onLMAXprice(priceObj) - } - } - } - - function onLMAXprice(priceObj) - { - if (!hedgeAllowed) return - if (direction() === 1) { - hedgePrice = priceObj.ask - } - else { - hedgePrice = priceObj.bid - } - - if (settled) { - return - } - - var price = 0.0 - if (direction() === 1) { - price = priceObj.ask - } - else { - price = priceObj.bid - } - - if (bestPrice) { - if (bestPrice != finalPrice) { // at least one quote exists - if (direction() === 1) { // and it's not ours - if ((bestPrice < hedgePrice) && (finalPrice < bestPrice)) { - price = Math.min(bestPrice + priceIncrement, hedgePrice) - } - } - else { - if ((bestPrice > hedgePrice) && (finalPrice > bestPrice)) { - price = Math.max(hedgePrice, bestPrice - priceIncrement) - } - } - if (price) { - log("beating competitor") - sendPrice(price) - } - } - else { - if (direction() === 1) { - if (finalPrice < price) { - log("improving own bid") - sendPrice(price) - } - } - else { - if (finalPrice > price) { - log("improving own ask") - sendPrice(price) - } - } - } - } - else { // no quote was sent before - log("sending first RFQ response") - sendPrice(price) - } - } - - function onLMAXintent(intentObj) - { - if ((intentObj.symbol !== security) || (intentObj.buy !== (direction() === 1))) { - return // not our intent - } - hedgeAllowed = intentObj.allowed - if (!intentObj.allowed) { - log('intent is not allowed - pulling reply') - pullQuoteReply() - initialPrice = 0 - finalPrice = 0 - } - } - - function isContraCur() - { - if( typeof isContraCur.value == 'undefined' ) { - isContraCur.value = (security.split("/")[1] === quoteReq.product); - } - return isContraCur.value; - } - - function contraCur(product) - { - var spl = security.split("/") - return (spl[1] === product) ? spl[0] : spl[1]; - } - - function checkBalance(price) - { - var value = quoteReq.quantity - var product = quoteReq.product - if (!quoteReq.isBuy) { - product = contraCur(quoteReq.product) - if (isContraCur()) { - value = quoteReq.quantity / price - } - else { - value = quoteReq.quantity * price - } - } - - var balance = accountBalance(product) - if (value > balance) { - log("Not enough balance for " + product + ": " + value + " > " + balance) - return false - } - return true - } - - function sendPrice(price) - { - if (finalPrice === price) { - log('price ' + price + ' not changed') - return - } - finalPrice = price - if (!initialPrice) { - initialPrice = price - } - - if (!checkBalance(price)) return - - log('sending new reply: ' + price) - sendQuoteReply(price) - sendOrderIntent(hedgePrice) - } - - function hedgeOrderBuy() - { - var orderBuy = (direction() === 1) ? 'true' : 'false' - return orderBuy - } - - function hedgeOrderAmount(price) - { - var amount = isContraCur() ? quoteReq.quantity / price : quoteReq.quantity; - return amount - } - - function isCC() - { - return (quoteReq.assetType == 3) - } - - function isXBT() - { - return (quoteReq.assetType == 2) - } - - function sendHedgeOrder(price) - { - if (!price) { - log('sendHedgeOrder: invalid zero price'); - return - } - - if (isCC()) { - return - } - if (isXBT()) { - if (quoteReq.quantity > 1.0) { - log('XBT amount exceeds limit: ' + quoteReq.quantity) - return - } - } - var order = '{"symbol":"' + security + '", "buy":' + hedgeOrderBuy() - + ', "amount":' + hedgeOrderAmount(price) + ', "price":' + price + '}' - log('sending order: ' + order) - sendExtConn('LMAX', 'order', order) - } - - function sendOrderIntent(price) - { // This request just signals LMAX connector that order will follow soon, - // if LMAX's reply on it is negative, quote reply should be pulled - // price is used here only to calculate contra qty - if (isCC() || settled) return - if (!price) { - return - } - var intent = '{"symbol":"' + security + '", "buy":' + hedgeOrderBuy() - + ', "amount":' + hedgeOrderAmount(price) + '}' - sendExtConn('LMAX', 'order_intent', intent) - } - - function direction() - { - if( typeof direction.value == 'undefined' ) { - if ((quoteReq.isBuy && !isContraCur()) || (!quoteReq.isBuy && isContraCur())) { - direction.value = 1; - } - else { - direction.value = -1; - } - } - return direction.value; - } -} + + onBestPriceChanged: { + if (!hedgeAllowed || settled) return + log('new best price: ' + bestPrice) + if (bestPrice === finalPrice) return // our quote + + if (hedgePrice) { + if (direction() === 1) { + if (bestPrice > hedgePrice) { + sendPrice(hedgePrice) + } + else { + log("give up - best price too low: " + bestPrice + " vs " + hedgePrice) + } + } + else { + if (bestPrice < hedgePrice) { + sendPrice(hedgePrice) + } + else { + log("give up - best price too high: " + bestPrice + " vs " + hedgePrice) + } + } + } + } + + onExtDataReceived: { +// log('from: ' + from + ', type: ' + type + ', msg:\n' + msg) + if (from === 'LMAX') { + var msgObj = JSON.parse(msg) + if (type === 'mdprices') { + onLMAXprices(msgObj) + } + else if (type === 'order_intent') { + onLMAXintent(msgObj) + } + } + } + + onSettled: { + settled = true + log(security + ' settled at ' + finalPrice) + sendHedgeOrder(hedgePrice) + + var subs = '{"symbol":"' + security + '"}' + sendExtConn('LMAX', 'unsubscribe_prices', subs) + } + + function onLMAXprices(msgObj) + { + for (var i in msgObj.prices) { + var priceObj = msgObj.prices[i] + if (priceObj.symbol === security) { + onLMAXprice(priceObj) + } + } + } + + function onLMAXprice(priceObj) + { + if (!hedgeAllowed) return + if (direction() === 1) { + hedgePrice = priceObj.ask + } + else { + hedgePrice = priceObj.bid + } + + if (settled) { + return + } + + var price = 0.0 + if (direction() === 1) { + price = priceObj.ask + } + else { + price = priceObj.bid + } + + if (bestPrice) { + if (bestPrice != finalPrice) { // at least one quote exists + if (direction() === 1) { // and it's not ours + if ((bestPrice < hedgePrice) && (finalPrice < bestPrice)) { + price = Math.min(bestPrice + priceIncrement, hedgePrice) + } + } + else { + if ((bestPrice > hedgePrice) && (finalPrice > bestPrice)) { + price = Math.max(hedgePrice, bestPrice - priceIncrement) + } + } + if (price) { + log("beating competitor") + sendPrice(price) + } + } + else { + if (direction() === 1) { + if (finalPrice < price) { + log("improving own bid") + sendPrice(price) + } + } + else { + if (finalPrice > price) { + log("improving own ask") + sendPrice(price) + } + } + } + } + else { // no quote was sent before + log("sending first RFQ response") + sendPrice(price) + } + } + + function onLMAXintent(intentObj) + { + if ((intentObj.symbol !== security) || (intentObj.buy !== (direction() === 1))) { + return // not our intent + } + hedgeAllowed = intentObj.allowed + if (!intentObj.allowed) { + log('intent is not allowed - pulling reply') + pullQuoteReply() + initialPrice = 0 + finalPrice = 0 + } + } + + function isContraCur() + { + if( typeof isContraCur.value == 'undefined' ) { + isContraCur.value = (security.split("/")[1] === quoteReq.product); + } + return isContraCur.value; + } + + function contraCur(product) + { + var spl = security.split("/") + return (spl[1] === product) ? spl[0] : spl[1]; + } + + function checkBalance(price) + { + var value = quoteReq.quantity + var product = quoteReq.product + if (!quoteReq.isBuy) { + product = contraCur(quoteReq.product) + if (isContraCur()) { + value = quoteReq.quantity / price + } + else { + value = quoteReq.quantity * price + } + } + + var balance = accountBalance(product) + if (value > balance) { + log("Not enough balance for " + product + ": " + value + " > " + balance) + return false + } + return true + } + + function sendPrice(price) + { + if (finalPrice === price) { + log('price ' + price + ' not changed') + return + } + finalPrice = price + if (!initialPrice) { + initialPrice = price + } + + if (!checkBalance(price)) return + + log('sending new reply: ' + price) + sendQuoteReply(price) + sendOrderIntent(hedgePrice) + } + + function hedgeOrderBuy() + { + var orderBuy = (direction() === 1) ? 'true' : 'false' + return orderBuy + } + + function hedgeOrderAmount(price) + { + var amount = isContraCur() ? quoteReq.quantity / price : quoteReq.quantity; + return amount + } + + function isCC() + { + return (quoteReq.assetType == 3) + } + + function isXBT() + { + return (quoteReq.assetType == 2) + } + + function sendHedgeOrder(price) + { + if (!price) { + log('sendHedgeOrder: invalid zero price'); + return + } + + if (isCC()) { + return + } + if (isXBT()) { + if (hedgeOrderAmount(price) > 1.0) { + log('XBT amount exceeds limit: ' + hedgeOrderAmount(price)) + return + } + } + var order = '{"symbol":"' + security + '", "buy":' + hedgeOrderBuy() + + ', "amount":' + hedgeOrderAmount(price) + ', "price":' + price + '}' + log('sending order: ' + order) + sendExtConn('LMAX', 'order', order) + } + + function sendOrderIntent(price) + { // This request just signals LMAX connector that order will follow soon, + // if LMAX's reply on it is negative, quote reply should be pulled + // price is used here only to calculate contra qty + if (isCC() || settled) return + if (!price) { + return + } + var intent = '{"symbol":"' + security + '", "buy":' + hedgeOrderBuy() + + ', "amount":' + hedgeOrderAmount(price) + '}' + sendExtConn('LMAX', 'order_intent', intent) + } + + function direction() + { + if( typeof direction.value == 'undefined' ) { + if ((quoteReq.isBuy && !isContraCur()) || (!quoteReq.isBuy && isContraCur())) { + direction.value = 1; + } + else { + direction.value = -1; + } + } + return direction.value; + } +} diff --git a/UnitTests/BlockchainMonitor.cpp b/UnitTests/BlockchainMonitor.cpp index 8d6c0d242..c3a6cef6a 100644 --- a/UnitTests/BlockchainMonitor.cpp +++ b/UnitTests/BlockchainMonitor.cpp @@ -53,7 +53,7 @@ std::vector BlockchainMonitor::waitForZC() auto zcVec = zcQueue_.pop_front(); return zcVec; } - catch (ArmoryThreading::IsEmpty&) + catch (Armory::Threading::IsEmpty&) {} std::this_thread::sleep_for(std::chrono::milliseconds{10}); diff --git a/UnitTests/BlockchainMonitor.h b/UnitTests/BlockchainMonitor.h index 5d77be270..2346789dc 100644 --- a/UnitTests/BlockchainMonitor.h +++ b/UnitTests/BlockchainMonitor.h @@ -16,7 +16,6 @@ #include #include "ArmoryConnection.h" #include "BinaryData.h" -#include "ClientClasses.h" #include "ThreadSafeClasses.h" @@ -53,7 +52,7 @@ class BlockchainMonitor : public ArmoryCallbackTarget private: std::atomic_bool receivedNewBlock_{ false }; - ArmoryThreading::BlockingQueue> zcQueue_; + Armory::Threading::BlockingQueue> zcQueue_; }; #endif // __BLOCKCHAIN_MONITOR_H__ diff --git a/UnitTests/CMakeLists.txt b/UnitTests/CMakeLists.txt index 0d5a2eb3e..c7adb7c63 100644 --- a/UnitTests/CMakeLists.txt +++ b/UnitTests/CMakeLists.txt @@ -50,10 +50,9 @@ INCLUDE_DIRECTORIES( ${BS_NETWORK_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${COMMON_LIB_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${CRYPTO_LIB_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${WALLET_LIB_INCLUDE_DIR} ) - INCLUDE_DIRECTORIES( ${BS_COMMUNICATION_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${PATH_TO_GENERATED} ) - +INCLUDE_DIRECTORIES( ${BLOCK_SETTLE_ROOT}/Core ) INCLUDE_DIRECTORIES( ${NETTY_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${BS_COMMON_ENUMS_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${BS_TERMINAL_API_INCLUDE_DIR} ) @@ -70,6 +69,7 @@ TARGET_COMPILE_DEFINITIONS( ${UNIT_TESTS} PRIVATE ) TARGET_LINK_LIBRARIES( ${UNIT_TESTS} + ${TERMINAL_CORE_NAME} ${BLOCKSETTLE_UI_LIBRARY_NAME} ${CPP_WALLET_LIB_NAME} ${BS_NETWORK_LIB_NAME} @@ -79,7 +79,6 @@ TARGET_LINK_LIBRARIES( ${UNIT_TESTS} ${BOTAN_LIB} ${COMMON_LIB} ${PROTO_LIB} - ${ZMQ_LIB} ${GTEST_LIB} ${BS_PROTO_LIB_NAME} ${AUTH_PROTO_LIB} diff --git a/UnitTests/MockAssetMgr.h b/UnitTests/MockAssetMgr.h index 80e05c4d7..e7c859c82 100644 --- a/UnitTests/MockAssetMgr.h +++ b/UnitTests/MockAssetMgr.h @@ -32,7 +32,7 @@ class MockAssetManager : public AssetManager public: MockAssetManager(const std::shared_ptr& logger) - : AssetManager(logger, nullptr, nullptr, nullptr) {} + : AssetManager(logger, nullptr) {} void init() override; std::vector privateShares(bool forceExt) override; diff --git a/UnitTests/MockTerminal.cpp b/UnitTests/MockTerminal.cpp new file mode 100644 index 000000000..1ff4ba799 --- /dev/null +++ b/UnitTests/MockTerminal.cpp @@ -0,0 +1,233 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "MockTerminal.h" +#include "Adapters/BlockchainAdapter.h" +#include "Adapters/WalletsAdapter.h" +#include "ArmorySettings.h" +#include "Message/Adapter.h" +#include "SettingsAdapter.h" +#include "SignerAdapter.h" +#include "TerminalMessage.h" +#include "TestEnv.h" + +#include "common.pb.h" +#include "terminal.pb.h" + +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; +using namespace bs::message; + +MockTerminalBus::MockTerminalBus(const std::shared_ptr& logger + , const std::string &name) + : logger_(logger) +{ + queue_ = std::make_shared(std::make_shared(logger) + , logger, name); +} + +MockTerminalBus::~MockTerminalBus() +{ + shutdown(); +} + +void MockTerminalBus::addAdapter(const std::shared_ptr& adapter) +{ + queue_->bindAdapter(adapter); + adapter->setQueue(queue_); + + static const auto& adminUser = UserTerminal::create(TerminalUsers::System); + for (const auto& user : adapter->supportedReceivers()) { + AdministrativeMessage msg; + msg.set_component_created(user->value()); + auto env = bs::message::Envelope::makeBroadcast(adminUser, msg.SerializeAsString()); + queue_->pushFill(env); + } +} + +void MockTerminalBus::start() +{ + static const auto& adminUser = UserTerminal::create(TerminalUsers::System); + AdministrativeMessage msg; + msg.mutable_start(); + auto env = bs::message::Envelope::makeBroadcast(adminUser, msg.SerializeAsString()); + queue_->pushFill(env); +} + +void MockTerminalBus::shutdown() +{ + queue_->terminate(); +} + +class SettingsMockAdapter : public bs::message::Adapter +{ +public: + SettingsMockAdapter(const std::shared_ptr& logger) + : logger_(logger) + , user_(std::make_shared(TerminalUsers::Settings)) + {} + ~SettingsMockAdapter() override = default; + + bool processBroadcast(const bs::message::Envelope& env) override + { + if (env.sender->value() == TerminalUsers::Blockchain) { + ArmoryMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse armory msg #{}", __func__, env.foreignId()); + return false; + } + if (msg.data_case() == ArmoryMessage::kSettingsRequest) { + ArmoryMessage msgReply; + auto msgResp = msgReply.mutable_settings_response(); + ArmorySettings armorySettings; + armorySettings.name = QLatin1Literal("test"); + armorySettings.netType = NetworkType::TestNet; + armorySettings.armoryDBIp = QLatin1String("127.0.0.1"); + armorySettings.armoryDBPort = 82; + msgResp->set_socket_type(armorySettings.socketType); + msgResp->set_network_type((int)armorySettings.netType); + msgResp->set_host(armorySettings.armoryDBIp.toStdString()); + msgResp->set_port(std::to_string(armorySettings.armoryDBPort)); + msgResp->set_bip15x_key(armorySettings.armoryDBKey.toStdString()); + msgResp->set_run_locally(armorySettings.runLocally); + msgResp->set_data_dir(armorySettings.dataDir.toStdString()); + msgResp->set_executable_path(armorySettings.armoryExecutablePath.toStdString()); + msgResp->set_bitcoin_dir(armorySettings.bitcoinBlocksDir.toStdString()); + msgResp->set_db_dir(armorySettings.dbDir.toStdString()); + pushResponse(user_, env, msgReply.SerializeAsString()); + return true; + } + } + return false; + } + + bool process(const bs::message::Envelope& env) override + { + if (env.receiver->value() == TerminalUsers::Settings) { + SettingsMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse settings msg #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case SettingsMessage::kGetRequest: + return processGetRequest(env, msg.get_request()); + case SettingsMessage::kPutRequest: break; + case SettingsMessage::kArmoryServer: break; + case SettingsMessage::kSetArmoryServer: break; + case SettingsMessage::kArmoryServersGet: break; + case SettingsMessage::kAddArmoryServer: break; + case SettingsMessage::kDelArmoryServer: break; + case SettingsMessage::kUpdArmoryServer: break; + case SettingsMessage::kSignerRequest: break; + case SettingsMessage::kSignerSetKey: break; + case SettingsMessage::kSignerReset: break; + case SettingsMessage::kSignerServersGet: break; + case SettingsMessage::kSetSignerServer: break; + case SettingsMessage::kAddSignerServer: break; + case SettingsMessage::kDelSignerServer: break; + case SettingsMessage::kStateGet: break; + case SettingsMessage::kReset: break; + case SettingsMessage::kResetToState: break; + case SettingsMessage::kLoadBootstrap: break; + case SettingsMessage::kGetBootstrap: break; + case SettingsMessage::kApiPrivkey: break; + case SettingsMessage::kApiClientKeys: break; + default: break; + } + } + return true; + } + + bs::message::Adapter::Users supportedReceivers() const override + { + return { user_ }; + } + + std::string name() const override { return "Settings"; } + +// std::shared_ptr createOnChainPlug() const; + +private: + bool processGetRequest(const bs::message::Envelope& env + , const BlockSettle::Terminal::SettingsMessage_SettingsRequest& request) + { + SettingsMessage msg; + auto msgResp = msg.mutable_get_response(); + for (const auto& req : request.requests()) { + if (req.source() == SettingSource_Local) { + switch (req.index()) { + case SetIdx_Initialized: { + auto resp = msgResp->add_responses(); + resp->set_allocated_request(new SettingRequest(req)); + resp->set_b(true); + } + break; + default: break; + } + } + } + if (msgResp->responses_size()) { + return pushResponse(user_, env, msg.SerializeAsString()); + } + return true; + } + +private: + std::shared_ptr logger_; + std::shared_ptr user_; +}; + + +class ApiMockAdapter : public bs::message::Adapter +{ +public: + ApiMockAdapter() {} + ~ApiMockAdapter() override = default; + + bool process(const bs::message::Envelope&) override { return true; } + bool processBroadcast(const bs::message::Envelope&) override { return false; } + + bs::message::Adapter::Users supportedReceivers() const override { + return { UserTerminal::create(TerminalUsers::API) }; + } + std::string name() const override { return "MockAPI"; } +}; + + +MockTerminal::MockTerminal(const std::shared_ptr& logger + , const std::string &name, const std::shared_ptr& signer + , const std::shared_ptr& armory) + : logger_(logger), name_(name) +{ + bus_ = std::make_shared(logger, name); + const auto& userBlockchain = UserTerminal::create(TerminalUsers::Blockchain); + const auto& userWallets = UserTerminal::create(TerminalUsers::Wallets); + const auto& signAdapter = std::make_shared(logger, signer); + bus_->addAdapter(std::make_shared()); + bus_->addAdapter(std::make_shared(logger)); + bus_->addAdapter(signAdapter); + //TODO: add TrackerMockAdapter + bus_->addAdapter(std::make_shared(logger_ + , userWallets, signAdapter->createClient(), userBlockchain)); + //bus_->addAdapter(std::make_shared(logger)); + bus_->addAdapter(std::make_shared(logger, userBlockchain + , armory)); +} + +void MockTerminal::start() +{ + bus_->start(); +} + +void MockTerminal::stop() +{ + bus_->shutdown(); +} diff --git a/UnitTests/MockTerminal.h b/UnitTests/MockTerminal.h new file mode 100644 index 000000000..1cee5b8eb --- /dev/null +++ b/UnitTests/MockTerminal.h @@ -0,0 +1,67 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef __MOCK_TERMINAL_H__ +#define __MOCK_TERMINAL_H__ + +#include +#include +#include "Message/Bus.h" + +namespace spdlog { + class logger; +} +namespace bs { + namespace sync { + } +} +class MockTerminal; +class TestArmoryConnection; +class WalletSignerContainer; + +class MockTerminalBus : public bs::message::Bus +{ + friend class MockTerminal; +public: + MockTerminalBus(const std::shared_ptr& + , const std::string &name); + ~MockTerminalBus() override; + + void addAdapter(const std::shared_ptr&) override; + +private: + void start(); + void shutdown(); + +private: + std::shared_ptr logger_; + std::shared_ptr queue_; +}; + +class MockTerminal +{ +public: + MockTerminal(const std::shared_ptr& logger + , const std::string &name, const std::shared_ptr & + , const std::shared_ptr &); + + std::shared_ptr bus() const { return bus_; } + std::string name() const { return name_; } + + void start(); + void stop(); + +private: + std::shared_ptr logger_; + std::shared_ptr bus_; + std::string name_; +}; + +#endif // __MOCK_TERMINAL_H__ diff --git a/UnitTests/TestAdapters.cpp b/UnitTests/TestAdapters.cpp new file mode 100644 index 000000000..46350581a --- /dev/null +++ b/UnitTests/TestAdapters.cpp @@ -0,0 +1,513 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#include "TestAdapters.h" +#include +#include +#include "MessageUtils.h" +#include "Wallets/ProtobufHeadlessUtils.h" +#include "TestEnv.h" + +#include "common.pb.h" +#include "headless.pb.h" +#include "terminal.pb.h" + +//#define MSG_DEBUGGING // comment this out if msg id debug output is not needed + +using namespace bs::message; +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; + +constexpr auto kExpirationTimeout = std::chrono::seconds{ 5 }; + +bool TestSupervisor::process(const Envelope &env) +{ +#ifdef MSG_DEBUGGING + StaticLogger::loggerPtr->debug("[{}] {}{}: {}({}) -> {}({}), {} bytes" + , name(), env.request ? '/' : '\\', env.id, env.sender->name() + , env.sender->value(), env.receiver ? env.receiver->name() : "null" + , env.receiver ? env.receiver->value() : -1, env.message.size()); +#endif //MSG_DEBUGGING + { + std::unique_lock lock(mtxWait_); + std::vector filtersToDelete; + for (const auto &filter : filterMultWait_) { + if (filter.second.first(env)) { + const auto seqNo = filter.first; + auto &filtPair = filterMap_[seqNo]; + filtPair.second.push_back(env); + if (filtPair.second.size() >= filtPair.first) { + filter.second.second->set_value(filtPair.second); + filtersToDelete.push_back(seqNo); + break; + } + return false; + } + } + for (const auto &filter : filterWait_) { + if (filter.second.first(env)) { + const auto seqNo = filter.first; + filter.second.second->set_value(env); + filtersToDelete.push_back(seqNo); + break; + } + } + if (!filtersToDelete.empty()) { + for (const auto &seqNo : filtersToDelete) { + filterMultWait_.erase(seqNo); + filterMap_.erase(seqNo); + filterWait_.erase(seqNo); + } + return false; + } + } + return true; +} + +bs::message::SeqId TestSupervisor::send(bs::message::TerminalUsers sender, bs::message::TerminalUsers receiver + , const std::string &message, bs::message::SeqId respId) +{ + return pushResponse(UserTerminal::create(sender), UserTerminal::create(receiver) + , message, respId); +} + +std::future> TestSupervisor::waitFor(const FilterCb &cb + , size_t count, uint32_t *seqNoExt) +{ + const auto seqNo = ++seqNo_; + if (seqNoExt != nullptr) { + *seqNoExt = seqNo; + } + auto promWait = std::make_shared>>(); + std::unique_lock lock(mtxWait_); + filterMultWait_[seqNo] = { cb, promWait }; + filterMap_[seqNo] = { count, {} }; + return promWait->get_future(); +} + +std::future TestSupervisor::waitFor(const FilterCb &cb + , uint32_t *seqNoExt) +{ + const auto seqNo = ++seqNo_; + if (seqNoExt != nullptr) { + *seqNoExt = seqNo; + } + auto promWait = std::make_shared>(); + std::unique_lock lock(mtxWait_); + filterWait_[seqNo] = { cb, promWait }; + return promWait->get_future(); +} + +void TestSupervisor::unwaitFor(const uint32_t seqNo) +{ + std::unique_lock lock(mtxWait_); + filterMultWait_.erase(seqNo); + filterMap_.erase(seqNo); + filterWait_.erase(seqNo); +} + + +MatchingMock::MatchingMock(const std::shared_ptr& logger + , const std::string& name, const std::string &email + , const std::shared_ptr& armoryInst) + : logger_(logger), name_(name), email_(email) + , armoryInst_(armoryInst) + , user_(UserTerminal::create(TerminalUsers::Matching)) + , userBS_(UserTerminal::create(TerminalUsers::BsServer)) + , userSettl_(UserTerminal::create(TerminalUsers::Settlement)) +{} + +bool MatchingMock::process(const bs::message::Envelope& env) +{ + if (env.isRequest() && (env.receiver->value() == user_->value())) { + MatchingMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own request #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case MatchingMessage::kSendRfq: + if (siblings_.empty()) { + logger_->warn("[{}] {}: no one to notify", __func__, name()); + //TODO: send result back to requester + } + else { + matches_[msg.send_rfq().id()] = { {}, msg.send_rfq().auth_pub_key() }; + for (const auto& sibling : siblings_) { + sibling->inject(msg, email_); + } + } + break; + + case MatchingMessage::kSubmitQuoteNotif: + if (siblings_.empty()) { + logger_->error("[{}] {}: no siblings", __func__, name()); + } else { + auto quote = msg.submit_quote_notif().quote(); + const auto& itMatch = matches_.find(quote.request_id()); + if (itMatch == matches_.end()) { + logger_->info("[{}] {}: not our quote", __func__, name()); + break; + } + quote.set_quote_id(CryptoPRNG::generateRandom(6).toHexStr()); + quote.set_quoting_type((int)bs::network::Quote::Tradeable); + auto msgCopy = msg; + *msgCopy.mutable_submit_quote_notif()->mutable_quote() = quote; + itMatch->second.quote = fromMsg(msgCopy.submit_quote_notif().quote()); + for (const auto& sibling : siblings_) { + sibling->inject(msgCopy, email_); + } + const auto& timeNow = bs::message::bus_clock::now(); + pushRequest(user_, user_, quote.quote_id() //FIXME: put actual quote's expirationTime + , timeNow + kExpirationTimeout); + + MatchingMessage msgSettl; + toMsg(itMatch->second.quote, msgSettl.mutable_quote()); + pushResponse(user_, userSettl_, msgSettl.SerializeAsString()); + } + break; + + case MatchingMessage::kOrder: { // only requester's processing + const auto& status = static_cast(msg.order().status()); + for (auto& match : matches_) { + if (match.second.quote.quoteId == msg.order().quote_id()) { + if ((status == bs::network::Order::Filled) || (status == bs::network::Order::Failed)) { + matches_.erase(match.first); + } + else { + match.second.order = fromMsg(msg.order()); + } + return pushResponse(user_, userSettl_, msg.SerializeAsString()); + } + } + } + break; + + case MatchingMessage::kAcceptRfq: { // only requester's processing + const auto& itMatch = matches_.find(msg.accept_rfq().rfq_id()); + if (itMatch == matches_.end()) { + logger_->warn("[{}] not our RFQ {}", __func__, msg.accept_rfq().rfq_id()); + break; + } + if (itMatch->second.quote.assetType == bs::network::Asset::SpotFX) { + sendFilledOrder(itMatch->first); + matches_.erase(itMatch); + for (const auto& sibling : siblings_) { + sibling->inject(msg, email_); + } + } + } + break; + default: break; + } + } + if (env.isRequest() && (env.receiver->value() == userBS_->value())) { + BsServerMessage msg; + if (!msg.ParseFromString(env.message)) { + logger_->error("[{}] failed to parse own request #{}", __func__, env.foreignId()); + return true; + } + switch (msg.data_case()) { + case BsServerMessage::kSendUnsignedPayin: + return processUnsignedPayin(msg.send_unsigned_payin()); + case BsServerMessage::kSendSignedPayin: + return processSignedTX(msg.send_signed_payin(), true, true); + case BsServerMessage::kSendSignedPayout: + return processSignedTX(msg.send_signed_payout(), false, true); + default: break; + } + } + else if (env.receiver->value() == env.sender->value() + && (env.sender->value() == user_->value())) { //own to self + for (const auto& match : matches_) { + if (match.second.quote.quoteId == env.message) { + return sendPendingOrder(match.first); + } + } + } + return true; +} + +bool MatchingMock::processBroadcast(const bs::message::Envelope&) +{ + return false; +} + +void MatchingMock::link(const std::shared_ptr& sibling) +{ + siblings_.insert(sibling); +} + +bool MatchingMock::inject(const MatchingMessage& msg, const std::string &email) +{ + switch (msg.data_case()) { + case MatchingMessage::kSendRfq: + return sendIncomingRFQ(msg.send_rfq(), email); + case MatchingMessage::kSubmitQuoteNotif: + return sendQuoteReply(msg.submit_quote_notif(), email); + case MatchingMessage::kAcceptRfq: // only for responder + return sendFilledOrder(msg.accept_rfq().rfq_id()); + + case MatchingMessage::kOrder: // only for requester + for (auto& match : matches_) { + if (match.second.quote.quoteId == msg.order().quote_id()) { + match.second.order = fromMsg(msg.order()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); + } + } + logger_->warn("[{}] match for {} not found", __func__, msg.order().quote_id()); + break; + default: break; + } + return true; +} + +bool MatchingMock::inject(const BsServerMessage& msg, const std::string& email) +{ + switch (msg.data_case()) { + case BsServerMessage::kUnsignedPayinRequested: + return sendUnsignedPayinRequest(msg.unsigned_payin_requested()); + case BsServerMessage::kSignedPayoutRequested: + return sendSignedPayoutRequest(msg.signed_payout_requested()); + case BsServerMessage::kSignedPayinRequested: + return sendSignedPayinRequest(msg.signed_payin_requested()); + case BsServerMessage::kSendSignedPayin: + return processSignedTX(msg.send_signed_payin(), true); + case BsServerMessage::kSendSignedPayout: + return processSignedTX(msg.send_signed_payout(), false); + default: break; + } + return true; +} + +bool MatchingMock::sendIncomingRFQ(const RFQ& rfq, const std::string &email) +{ + MatchingMessage msg; + auto msgInRFQ = msg.mutable_incoming_rfq(); + *msgInRFQ->mutable_rfq() = rfq; + Match match{ CryptoPRNG::generateRandom(4).toHexStr() }; + msgInRFQ->set_session_token(match.sesToken); + if (static_cast(rfq.asset_type()) == bs::network::Asset::SpotXBT) { + msgInRFQ->set_settlement_id(CryptoPRNG::generateRandom(32).toHexStr()); + } + msgInRFQ->set_party(email); + const auto& timeNow = std::chrono::system_clock::now(); + msgInRFQ->set_timestamp_ms(std::chrono::duration_cast( + timeNow.time_since_epoch()).count()); + msgInRFQ->set_expiration_ms(std::chrono::duration_cast( + (timeNow + kExpirationTimeout).time_since_epoch()).count()); + matches_[rfq.id()] = std::move(match); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingMock::sendQuoteReply(const ReplyToRFQ& reply, const std::string& email) +{ + const auto& itMatch = matches_.find(reply.quote().request_id()); + if (itMatch == matches_.end()) { + logger_->info("[{}] {}: not our quote", __func__, name()); + return true; + } + itMatch->second.quote = fromMsg(reply.quote()); + itMatch->second.quote.requestorAuthPublicKey = itMatch->second.reqAuthPubKey; + itMatch->second.sesToken = reply.session_token(); + return sendQuote(itMatch->second.quote); +} + +bool MatchingMock::sendQuote(const bs::network::Quote& quote) +{ + MatchingMessage msg; + toMsg(quote, msg.mutable_quote()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingMock::sendPendingOrder(const std::string& rfqId) +{ + const auto& itMatch = matches_.find(rfqId); + if (itMatch == matches_.end()) { + logger_->error("[{}] {}: unknown RFQ {}", __func__, name(), rfqId); + return true; + } + bs::network::Order order; + order.clOrderId = CryptoPRNG::generateRandom(7).toHexStr(); + order.exchOrderId = CryptoPRNG::generateRandom(8).toHexStr(); + order.quoteId = itMatch->second.quote.quoteId; + order.dateTime = std::chrono::system_clock::now(); + order.security = itMatch->second.quote.security; + order.product = itMatch->second.quote.product; + order.settlementId = BinaryData::CreateFromHex(itMatch->second.quote.settlementId); + order.quantity = itMatch->second.quote.quantity; + order.price = itMatch->second.quote.price; + order.avgPx = itMatch->second.quote.price; + order.assetType = itMatch->second.quote.assetType; + order.side = itMatch->second.quote.side; + order.status = bs::network::Order::Pending; + itMatch->second.order = order; + MatchingMessage msg; + toMsg(order, msg.mutable_order()); + pushResponse(user_, userSettl_, msg.SerializeAsString()); + + order.side = bs::network::Side::invert(order.side); + toMsg(order, msg.mutable_order()); + for (const auto& sibling : siblings_) { + sibling->inject(msg, email_); + } + + if (itMatch->second.quote.assetType == bs::network::Asset::SpotXBT) { + const auto& settlementId = BinaryData::CreateFromHex(itMatch->second.quote.settlementId); + BsServerMessage msgBS; + msgBS.set_unsigned_payin_requested(settlementId.toBinStr()); + if (itMatch->second.isSellXBT()) { + for (const auto& sibling : siblings_) { + sibling->inject(msgBS, email_); + } + } + else { + pushResponse(userBS_, userSettl_, msgBS.SerializeAsString()); + } + } + return true; +} + +bool MatchingMock::sendFilledOrder(const std::string& rfqId) +{ + const auto& itMatch = matches_.find(rfqId); + if (itMatch == matches_.end()) { + return true; // not our RFQ + } + itMatch->second.order.status = bs::network::Order::Filled; + MatchingMessage msg; + toMsg(itMatch->second.order, msg.mutable_order()); + return pushResponse(user_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingMock::sendUnsignedPayinRequest(const std::string& settlIdBin) +{ + const auto& settlIdHex = BinaryData::fromString(settlIdBin).toHexStr(); + const auto& itMatch = std::find_if(matches_.cbegin(), matches_.cend() + , [settlIdHex](const std::pair& match) { + return (match.second.quote.settlementId == settlIdHex); + }); + if (itMatch == matches_.end()) { + logger_->warn("[{}] can't find match with settlement id {}", __func__, settlIdHex); + return true; + } + BsServerMessage msgBS; + msgBS.set_unsigned_payin_requested(settlIdBin); + return pushResponse(userBS_, userSettl_, msgBS.SerializeAsString()); +} + +bool MatchingMock::sendSignedPayinRequest(const BsServerMessage_SignXbtHalf& request) +{ + const auto& settlIdHex = BinaryData::fromString(request.settlement_id()).toHexStr(); + const auto& itMatch = std::find_if(matches_.cbegin(), matches_.cend() + , [settlIdHex](const std::pair& match) { + return (match.second.quote.settlementId == settlIdHex); + }); + if (itMatch == matches_.end()) { + logger_->warn("[{}] can't find match with settlement id {}", __func__, settlIdHex); + return true; + } + BsServerMessage msgBS; + *msgBS.mutable_signed_payin_requested() = request; + return pushResponse(userBS_, userSettl_, msgBS.SerializeAsString()); +} + +bool MatchingMock::sendSignedPayoutRequest(const BsServerMessage_SignXbtHalf& request) +{ + const auto& settlIdHex = BinaryData::fromString(request.settlement_id()).toHexStr(); + const auto& itMatch = std::find_if(matches_.cbegin(), matches_.cend() + , [settlIdHex](const std::pair& match) { + return (match.second.quote.settlementId == settlIdHex); + }); + if (itMatch == matches_.end()) { + logger_->warn("[{}] can't find match with settlement id {}", __func__, settlIdHex); + return true; + } + BsServerMessage msgBS; + *msgBS.mutable_signed_payout_requested() = request; + return pushResponse(userBS_, userSettl_, msgBS.SerializeAsString()); +} + +bool MatchingMock::processUnsignedPayin(const BsServerMessage_XbtTransaction& response) +{ + Codec_SignerState::SignerState signerState; + if (!signerState.ParseFromString(response.tx())) { + logger_->error("[{}] failed to parse SignerState", __func__); + return true; //TODO: send settlement failure? + } + bs::core::wallet::TXSignRequest txReq; + txReq.armorySigner_.deserializeState(signerState); + const auto& payinHash = txReq.txId(); + if (payinHash.empty()) { + logger_->error("[{}] invalid payin hash", __func__); + return true; //TODO: send settlement failure? + } + BsServerMessage msg; + auto msgReq = msg.mutable_signed_payout_requested(); + msgReq->set_settlement_id(response.settlement_id()); + msgReq->set_payin_hash(payinHash.toBinStr()); + for (const auto& sibling : siblings_) { + sibling->inject(msg, email_); + } + msgReq = msg.mutable_signed_payin_requested(); + msgReq->set_settlement_id(response.settlement_id()); + msgReq->set_unsigned_payin(response.tx()); + msgReq->set_payin_hash(payinHash.toBinStr()); + const auto curTime = QDateTime::currentDateTime(); + msgReq->set_timestamp(curTime.toMSecsSinceEpoch()); + return pushResponse(userBS_, userSettl_, msg.SerializeAsString()); +} + +bool MatchingMock::processSignedTX(const BsServerMessage_XbtTransaction& response + , bool payin, bool recurse) +{ + const auto& settlIdHex = BinaryData::fromString(response.settlement_id()).toHexStr(); + const auto &itMatch = std::find_if(matches_.begin(), matches_.end() + , [settlIdHex](const std::pair &match) { + return (match.second.quote.settlementId == settlIdHex); + }); + if (itMatch == matches_.end()) { + logger_->warn("[{}] unknown settlement id {}", __func__, settlIdHex); + return true; + } + { + const auto& tx = BinaryData::fromString(response.tx()); + if (payin) { + itMatch->second.signedPayin = tx; + } else { + itMatch->second.signedPayout = tx; + } + } + if (!itMatch->second.signedPayin.empty() && !itMatch->second.signedPayout.empty()) { + armoryInst_->pushZC(itMatch->second.signedPayin); + armoryInst_->pushZC(itMatch->second.signedPayout); + + //TODO: monitor new blocks and provide gradual order status update on each conf + sendFilledOrder(itMatch->first); + } + else if (recurse) { + BsServerMessage msg; + if (payin) { + *msg.mutable_send_signed_payin() = response; + } else { + *msg.mutable_send_signed_payout() = response; + } + for (const auto& sibling : siblings_) { + sibling->inject(msg, email_); + } + } + return true; +} + +bool MatchingMock::Match::isSellXBT() const +{ + return ((quote.side == bs::network::Side::Buy) + || (quote.product != bs::network::XbtCurrency)); +} diff --git a/UnitTests/TestAdapters.h b/UnitTests/TestAdapters.h new file mode 100644 index 000000000..657ada321 --- /dev/null +++ b/UnitTests/TestAdapters.h @@ -0,0 +1,129 @@ +/* + +*********************************************************************************** +* Copyright (C) 2020 - 2021, BlockSettle AB +* Distributed under the GNU Affero General Public License (AGPL v3) +* See LICENSE or http://www.gnu.org/licenses/agpl.html +* +********************************************************************************** + +*/ +#ifndef TEST_ADAPTERS_H +#define TEST_ADAPTERS_H + +#include +#include +#include +#include "CommonTypes.h" +#include "Message/Adapter.h" +#include "TerminalMessage.h" + +namespace spdlog { + class logger; +} +namespace BlockSettle { + namespace Terminal { + class BsServerMessage; + class BsServerMessage_SignXbtHalf; + class BsServerMessage_XbtTransaction; + class MatchingMessage; + class RFQ; + class ReplyToRFQ; + } +} +struct ArmoryInstance; + +class TestSupervisor : public bs::message::Adapter +{ +public: + TestSupervisor(const std::string& name) : name_(name) + {} + + bool process(const bs::message::Envelope &) override; + + bool processBroadcast(const bs::message::Envelope& env) override + { + return process(env); + } + + bs::message::Adapter::Users supportedReceivers() const override + { + return { std::make_shared() }; + } + std::string name() const override { return "sup" + name_; } + + bs::message::SeqId send(bs::message::TerminalUsers sender, bs::message::TerminalUsers receiver + , const std::string &message, bs::message::SeqId respId = 0); + bool pushFill(bs::message::Envelope &env) { return bs::message::Adapter::pushFill(env); } + + using FilterCb = std::function; + std::future> waitFor(const FilterCb &, size_t count + , uint32_t *seqNo = nullptr); + std::future waitFor(const FilterCb &, uint32_t *seqNo = nullptr); + void unwaitFor(const uint32_t seqNo); + +private: + std::string name_; + std::map>> filterMap_; + std::map>>>> filterMultWait_; + std::map>>> filterWait_; + std::mutex mtxWait_; + std::atomic_uint32_t seqNo_{ 0 }; +}; + + +class MatchingMock : public bs::message::Adapter +{ +public: + MatchingMock(const std::shared_ptr& logger + , const std::string& name, const std::string& email + , const std::shared_ptr &); // for pushing ZCs (mocking PB) + + bool process(const bs::message::Envelope&) override; + bool processBroadcast(const bs::message::Envelope&) override; + + bs::message::Adapter::Users supportedReceivers() const override + { + return { user_, userBS_ }; + } + std::string name() const override { return "Match" + name_; } + + void link(const std::shared_ptr&); + bool inject(const BlockSettle::Terminal::MatchingMessage&, const std::string& email); + bool inject(const BlockSettle::Terminal::BsServerMessage&, const std::string& email); + +private: + bool sendIncomingRFQ(const BlockSettle::Terminal::RFQ&, const std::string &email); + bool sendQuoteReply(const BlockSettle::Terminal::ReplyToRFQ&, const std::string& email); + bool sendQuote(const bs::network::Quote&); + bool sendPendingOrder(const std::string& rfqId); + bool sendFilledOrder(const std::string& rfqId); + + bool sendUnsignedPayinRequest(const std::string& settlIdBin); + bool sendSignedPayinRequest(const BlockSettle::Terminal::BsServerMessage_SignXbtHalf&); + bool sendSignedPayoutRequest(const BlockSettle::Terminal::BsServerMessage_SignXbtHalf&); + bool processUnsignedPayin(const BlockSettle::Terminal::BsServerMessage_XbtTransaction&); + bool processSignedTX(const BlockSettle::Terminal::BsServerMessage_XbtTransaction& + , bool payin, bool recurse = false); + +private: + std::shared_ptr logger_; + std::shared_ptr user_, userBS_, userSettl_; + std::string name_, email_; + std::shared_ptr armoryInst_; + + std::set> siblings_; + + struct Match { + std::string sesToken; + std::string reqAuthPubKey; + bs::network::Quote quote; + bs::network::Order order; + BinaryData signedPayin; + BinaryData signedPayout; + + bool isSellXBT() const; + }; + std::unordered_map matches_; +}; +#endif // TEST_ADAPTERS_H diff --git a/UnitTests/TestAddress.cpp b/UnitTests/TestAddress.cpp index fe44b1f8e..2b0851bd3 100644 --- a/UnitTests/TestAddress.cpp +++ b/UnitTests/TestAddress.cpp @@ -9,8 +9,9 @@ */ #include - #include "Address.h" +#include "BitcoinSettings.h" + TEST(TestAddress, ValidScenarios) { @@ -35,7 +36,7 @@ TEST(TestAddress, ValidScenarios) auto addr = bs::Address::fromPubKey(pubkey1, AddressEntryType_P2PKH); BinaryData prefixedHash; - prefixedHash.append(NetworkConfig::getPubkeyHashPrefix()); + prefixedHash.append(Armory::Config::BitcoinSettings::getPubkeyHashPrefix()); prefixedHash.append(pubkeyHash1); EXPECT_EQ(prefixedHash, addr.prefixed()); @@ -73,12 +74,12 @@ TEST(TestAddress, ValidScenarios) { //P2SH BinaryData prefixed; - prefixed.append(NetworkConfig::getScriptHashPrefix()); + prefixed.append(Armory::Config::BitcoinSettings::getScriptHashPrefix()); prefixed.append(rando20); auto addr = bs::Address::fromHash(prefixed); BinaryData prefixedHash; - prefixedHash.append(NetworkConfig::getScriptHashPrefix()); + prefixedHash.append(Armory::Config::BitcoinSettings::getScriptHashPrefix()); prefixedHash.append(rando20); EXPECT_EQ(prefixedHash, addr.prefixed()); @@ -100,7 +101,7 @@ TEST(TestAddress, ValidScenarios) BinaryData script = BtcUtils::getP2PKHScript(pubkeyHash1); BinaryData prefixedHash; - prefixedHash.append(NetworkConfig::getScriptHashPrefix()); + prefixedHash.append(Armory::Config::BitcoinSettings::getScriptHashPrefix()); prefixedHash.append(BtcUtils::getHash160(script)); EXPECT_EQ(prefixedHash, addr.prefixed()); @@ -121,7 +122,7 @@ TEST(TestAddress, ValidScenarios) BinaryData script = BtcUtils::getP2WPKHOutputScript(pubkeyHash1); BinaryData prefixedHash; - prefixedHash.append(NetworkConfig::getScriptHashPrefix()); + prefixedHash.append(Armory::Config::BitcoinSettings::getScriptHashPrefix()); prefixedHash.append(BtcUtils::getHash160(script)); EXPECT_EQ(prefixedHash, addr.prefixed()); @@ -145,7 +146,7 @@ TEST(TestAddress, ValidScenarios) EXPECT_EQ(hash, addr.unprefixed()); BinaryData prefixedHash; - prefixedHash.append(NetworkConfig::getScriptHashPrefix()); + prefixedHash.append(Armory::Config::BitcoinSettings::getScriptHashPrefix()); prefixedHash.append(hash); EXPECT_EQ(prefixedHash, addr.prefixed()); @@ -177,7 +178,7 @@ TEST(TestAddress, ValidScenarios) EXPECT_EQ(addr.unprefixed(), hash); BinaryData prefixedHash; - prefixedHash.append(NetworkConfig::getScriptHashPrefix()); + prefixedHash.append(Armory::Config::BitcoinSettings::getScriptHashPrefix()); prefixedHash.append(hash); EXPECT_EQ(prefixedHash, addr.prefixed()); @@ -491,7 +492,7 @@ TEST(TestAddress, FromRecipient) { //p2pkh auto addr = bs::Address::fromPubKey(pubkey, AddressEntryType_P2PKH); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -503,7 +504,7 @@ TEST(TestAddress, FromRecipient) { //p2wpkh auto addr = bs::Address::fromPubKey(pubkey, AddressEntryType_P2WPKH); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -518,7 +519,7 @@ TEST(TestAddress, FromRecipient) prefixed.append(AddressEntry::getPrefixByte(AddressEntryType_P2SH)); prefixed.append(CryptoPRNG::generateRandom(20)); auto addr = bs::Address::fromHash(prefixed); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -531,7 +532,7 @@ TEST(TestAddress, FromRecipient) //p2sh - p2pk auto addr = bs::Address::fromPubKey(pubkey, AddressEntryType(AddressEntryType_P2SH | AddressEntryType_P2PK)); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -544,7 +545,7 @@ TEST(TestAddress, FromRecipient) //p2sh - p2pkh auto addr = bs::Address::fromPubKey(pubkey, AddressEntryType(AddressEntryType_P2SH | AddressEntryType_P2PKH)); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -557,7 +558,7 @@ TEST(TestAddress, FromRecipient) //p2sh - p2wpkh auto addr = bs::Address::fromPubKey(pubkey, AddressEntryType(AddressEntryType_P2SH | AddressEntryType_P2WPKH)); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -580,7 +581,7 @@ TEST(TestAddress, FromRecipient) //p2wsh | multisig auto addr = bs::Address::fromMultisigScript(msScript, AddressEntryType_P2WSH); - auto recipient = addr.getRecipient(bs::XBTAmount((uint64_t)COIN)); + auto recipient = addr.getRecipient(bs::XBTAmount((int64_t)COIN)); auto addrFromRecipient = bs::Address::fromRecipient(recipient); EXPECT_EQ(addrFromRecipient.unprefixed(), addr.unprefixed()); @@ -612,7 +613,7 @@ TEST(TestAddress, P2SH_ImplicitDetection) const auto& prefixed = addr.prefixed(); ASSERT_EQ(prefixed.getSize(), 21); EXPECT_EQ(prefixed, scriptHash); - EXPECT_EQ(prefixed.getPtr()[0], NetworkConfig::getScriptHashPrefix()); + EXPECT_EQ(prefixed.getPtr()[0], Armory::Config::BitcoinSettings::getScriptHashPrefix()); } TEST(TestAddress, P2SH_ExplicitDetection) @@ -649,7 +650,7 @@ TEST(TestAddress, P2SH_ExplicitDetection) const auto& prefixed = addr.prefixed(); ASSERT_EQ(prefixed.getSize(), 21); EXPECT_EQ(prefixed.getSliceCopy(1, 20), scriptHash); - EXPECT_EQ(prefixed.getPtr()[0], NetworkConfig::getScriptHashPrefix()); + EXPECT_EQ(prefixed.getPtr()[0], Armory::Config::BitcoinSettings::getScriptHashPrefix()); } { @@ -667,7 +668,7 @@ TEST(TestAddress, P2SH_ExplicitDetection) const auto& prefixed = addr.prefixed(); ASSERT_EQ(prefixed.getSize(), 21); EXPECT_EQ(prefixed.getSliceCopy(1, 20), scriptHash); - EXPECT_EQ(prefixed.getPtr()[0], NetworkConfig::getScriptHashPrefix()); + EXPECT_EQ(prefixed.getPtr()[0], Armory::Config::BitcoinSettings::getScriptHashPrefix()); } { @@ -685,7 +686,7 @@ TEST(TestAddress, P2SH_ExplicitDetection) const auto& prefixed = addr.prefixed(); ASSERT_EQ(prefixed.getSize(), 21); EXPECT_EQ(prefixed.getSliceCopy(1, 20), scriptHash); - EXPECT_EQ(prefixed.getPtr()[0], NetworkConfig::getScriptHashPrefix()); + EXPECT_EQ(prefixed.getPtr()[0], Armory::Config::BitcoinSettings::getScriptHashPrefix()); } } diff --git a/UnitTests/TestAuth.cpp b/UnitTests/TestAuth.cpp index 73dd7f7cc..27e79a161 100644 --- a/UnitTests/TestAuth.cpp +++ b/UnitTests/TestAuth.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -58,16 +58,17 @@ check unflagged return #include "CheckRecipSigner.h" #include "CoreHDWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "Wallets/SyncHDLeaf.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" -#include "AddressVerificator.h" #include "TestAuth.h" -using namespace ArmorySigner; +using namespace Armory::Signer; +#if 0 // Auth address code turned off /////////////////////////////////////////////////////////////////////////////// void TestValidationACT::onRefresh(const std::vector& ids, bool online) { @@ -127,7 +128,7 @@ BinaryData TestAuth::sendTo(uint64_t value, bs::Address& addr) signer.addSpender(spendPtr); - signer.addRecipient(addr.getRecipient(bs::XBTAmount{value})); + signer.addRecipient(addr.getRecipient(bs::XBTAmount{(int64_t)value})); signer.setFeed(coinbaseFeed_); //sign & send @@ -250,7 +251,7 @@ void TestAuth::SetUp() } //setup sync manager - auto inprocSigner = std::make_shared(priWallet_, envPtr_->logger()); + auto inprocSigner = std::make_shared(priWallet_, this, envPtr_->logger()); inprocSigner->Start(); syncMgr_ = std::make_shared(envPtr_->logger(), envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1293,5 +1294,5 @@ TEST_F(TestAuth, Concurrency_WithACT) EXPECT_FALSE(AuthAddressLogic::isValid(*vam, authAddresses[4])); EXPECT_FALSE(AuthAddressLogic::isValid(*vam, authAddresses[5])); } - +#endif //0 //reorg & zc replacement test diff --git a/UnitTests/TestAuth.h b/UnitTests/TestAuth.h index c7f717805..5f2574ec4 100644 --- a/UnitTests/TestAuth.h +++ b/UnitTests/TestAuth.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -19,9 +19,9 @@ #include #include "Address.h" #include "BlockchainMonitor.h" +#include "Wallets/SignContainer.h" #include "TestEnv.h" -#include "AuthAddressLogic.h" namespace bs { namespace sync { @@ -36,8 +36,10 @@ namespace bs { class Wallet; } } +class QtHCT; //////////////////////////////////////////////////////////////////////////////// +#if 0 // Auth address code turned off class TestValidationACT : public ValidationAddressACT { private: @@ -104,7 +106,7 @@ class TestValidationACT : public ValidationAddressACT }; //////////////////////////////////////////////////////////////////////////////// -class TestAuth : public ::testing::Test +class TestAuth : public ::testing::Test, public SignerCallbackTarget { protected: void SetUp() override; @@ -124,6 +126,7 @@ class TestAuth : public ::testing::Test std::shared_ptr xbtSignWallet_; std::shared_ptr xbtWallet_; std::shared_ptr syncMgr_; + std::shared_ptr hct_; bs::Address recvAddr_; std::shared_ptr envPtr_; @@ -150,5 +153,6 @@ class TestAuth : public ::testing::Test SecureBinaryData passphrase_; }; +#endif //0 -#endif \ No newline at end of file +#endif diff --git a/UnitTests/TestAuthEid.cpp b/UnitTests/TestAuthEid.cpp index eb25b1b31..5f65bb2cd 100644 --- a/UnitTests/TestAuthEid.cpp +++ b/UnitTests/TestAuthEid.cpp @@ -12,7 +12,7 @@ #include #include "BtcUtils.h" #include "bs_communication.pb.h" -#include "AutheIDClient.h" +//#include "AutheIDClient.h" namespace { @@ -32,6 +32,7 @@ namespace { } +#if 0 TEST(TestAuthEid, VerifySignature) { auto testResult = R"( @@ -101,3 +102,4 @@ TEST(TestAuthEid, VerifySignature) result = AutheIDClient::verifySignature(signResultInvalid, AuthEidEnv::Prod); EXPECT_TRUE(!result.valid); } +#endif //0 diff --git a/UnitTests/TestCCoin.cpp b/UnitTests/TestCCoin.cpp index b6d0531e5..279bfcbca 100644 --- a/UnitTests/TestCCoin.cpp +++ b/UnitTests/TestCCoin.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -15,13 +15,15 @@ #include "CheckRecipSigner.h" #include "CoreHDWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "Wallets/SyncHDLeaf.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" using namespace ArmorySigner; +#if 0 // CC code turned off TestCCoin::TestCCoin() {} @@ -89,7 +91,8 @@ void TestCCoin::SetUp() } } - auto inprocSigner = std::make_shared(envPtr_->walletsMgr(), envPtr_->logger(), "", NetworkType::TestNet); + auto inprocSigner = std::make_shared(envPtr_->walletsMgr() + , envPtr_->logger(), this, "", NetworkType::TestNet); inprocSigner->Start(); syncMgr_ = std::make_shared(envPtr_->logger(), envPtr_->appSettings(), envPtr_->armoryConnection()); syncMgr_->setSignContainer(inprocSigner); @@ -244,7 +247,7 @@ BinaryData TestCCoin::FundFromCoinbase( throw std::runtime_error("Not enough cb coins! Mine more blocks!"); for (auto && addr : addresses) { - signer.addRecipient(addr.getRecipient(bs::XBTAmount{ valuePerOne })); + signer.addRecipient(addr.getRecipient(bs::XBTAmount{ (int64_t)valuePerOne })); } signer.setFeed(coinbaseFeed_); @@ -258,7 +261,8 @@ BinaryData TestCCoin::FundFromCoinbase( return signer.getTxId(); } -BinaryData TestCCoin::SimpleSendMany(const bs::Address & fromAddress, const std::vector & toAddresses, const uint64_t & valuePerOne) +BinaryData TestCCoin::SimpleSendMany(const bs::Address & fromAddress + , const std::vector & toAddresses, const uint64_t & valuePerOne) { auto promPtr = std::make_shared>(); auto fut = promPtr->get_future(); @@ -275,7 +279,7 @@ BinaryData TestCCoin::SimpleSendMany(const bs::Address & fromAddress, const std: { std::vector> recipients; for(const auto & addr : toAddresses) { - recipients.push_back(addr.getRecipient(bs::XBTAmount{ valuePerOne })); + recipients.push_back(addr.getRecipient(bs::XBTAmount{ (int64_t)valuePerOne })); } const uint64_t requiredValue = valuePerOne * toAddresses.size(); @@ -370,7 +374,7 @@ Tx TestCCoin::CreateCJtx( } //CC recipients - cjSigner.addRecipient(structB.ccAddr_.getRecipient(bs::XBTAmount{ structB.ccValue_ })); + cjSigner.addRecipient(structB.ccAddr_.getRecipient(bs::XBTAmount{ (int64_t)structB.ccValue_ })); const bs::Address ccChange = structA.ccChange.empty() ? structA.ccAddr_ : structA.ccChange; if (ccValue < structB.ccValue_) @@ -383,14 +387,14 @@ Tx TestCCoin::CreateCJtx( auto factor = changeVal / ccLotSize_; changeVal = ccLotSize_ * factor; } - cjSigner.addRecipient(ccChange.getRecipient(bs::XBTAmount{ changeVal })); + cjSigner.addRecipient(ccChange.getRecipient(bs::XBTAmount{ (int64_t)changeVal })); } //XBT recipients - cjSigner.addRecipient(structA.xbtAddr_.getRecipient(bs::XBTAmount{ structA.xbtValue_ })); + cjSigner.addRecipient(structA.xbtAddr_.getRecipient(bs::XBTAmount{ (int64_t)structA.xbtValue_ })); if(xbtValue - structA.xbtValue_ - fee > 0) - cjSigner.addRecipient(structB.xbtAddr_.getRecipient(bs::XBTAmount{ xbtValue - structA.xbtValue_ - fee })); + cjSigner.addRecipient(structB.xbtAddr_.getRecipient(bs::XBTAmount{ (int64_t)(xbtValue - structA.xbtValue_ - fee) })); //grab unsigned tx if applicable Tx unsignedTx; @@ -2359,7 +2363,7 @@ TEST_F(TestCCoin, ZeroConfChain) { Signer signer; signer.addSpender(spender); - signer.addRecipient(addr.getRecipient(bs::XBTAmount{ value })); + signer.addRecipient(addr.getRecipient(bs::XBTAmount{ (int64_t)value })); auto script = spender->getOutputScript(); auto changeAddr = BtcUtils::getScrAddrForScript(script); @@ -2482,7 +2486,7 @@ TEST_F(TestCCoin, Reorg) for (auto& recipient : recipients) { total += recipient.second; - signer.addRecipient(recipient.first.getRecipient(bs::XBTAmount{ recipient.second })); + signer.addRecipient(recipient.first.getRecipient(bs::XBTAmount{ (int64_t)recipient.second })); } if (total > spender->getValue()) @@ -2844,7 +2848,7 @@ TEST_F(TestCCoin, Reorg_WithACT) for (auto& recipient : recipients) { total += recipient.second; - signer.addRecipient(recipient.first.getRecipient(bs::XBTAmount{ recipient.second })); + signer.addRecipient(recipient.first.getRecipient(bs::XBTAmount{ (int64_t)recipient.second })); } if (total > spender->getValue()) @@ -3151,12 +3155,12 @@ TEST_F(TestCCoin, DestroyCC) } //recipients - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(25 * COIN)))); - signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(uint64_t(20 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(25 * COIN)))); + signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(int64_t(20 * COIN)))); - signer.addRecipient(userCCAddresses_[3].getRecipient(bs::XBTAmount(100 * ccLotSize_))); - signer.addRecipient(userCCAddresses_[4].getRecipient(bs::XBTAmount(120 * ccLotSize_))); - signer.addRecipient(userCCAddresses_[5].getRecipient(bs::XBTAmount(70 * ccLotSize_))); + signer.addRecipient(userCCAddresses_[3].getRecipient(bs::XBTAmount((int64_t)100 * ccLotSize_))); + signer.addRecipient(userCCAddresses_[4].getRecipient(bs::XBTAmount((int64_t)120 * ccLotSize_))); + signer.addRecipient(userCCAddresses_[5].getRecipient(bs::XBTAmount((int64_t)70 * ccLotSize_))); //signing for (auto& utxo : utxoVec) { @@ -3235,7 +3239,7 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addSpender(std::make_shared(xbtUtxos1.back())); signer.addRecipient(userCCAddresses_[3].getRecipient(bs::XBTAmount(100 * ccLotSize_))); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(50 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(50 * COIN)))); auto unsignedTxRaw = signer.serializeUnsignedTx(); Tx txUnsigned(unsignedTxRaw); @@ -3272,7 +3276,7 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addSpender(std::make_shared(ccUtxos3.back())); signer.addRecipient(userCCAddresses_[3].getRecipient(bs::XBTAmount(200 * ccLotSize_))); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(50 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(50 * COIN)))); auto unsignedTxRaw = signer.serializeUnsignedTx(); Tx txUnsigned(unsignedTxRaw); @@ -3309,7 +3313,7 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addSpender(std::make_shared(xbtUtxos3.back())); signer.addRecipient(userCCAddresses_[3].getRecipient(bs::XBTAmount(100 * ccLotSize_))); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(120 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(120 * COIN)))); auto unsignedTxRaw = signer.serializeUnsignedTx(); Tx txUnsigned(unsignedTxRaw); @@ -3374,7 +3378,7 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addSpender(std::make_shared(xbtUtxos2.back())); signer.addSpender(std::make_shared(xbtUtxos3.back())); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(149 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(149 * COIN)))); auto unsignedTxRaw = signer.serializeUnsignedTx(); Tx txUnsigned(unsignedTxRaw); @@ -3401,8 +3405,8 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addRecipient(userCCAddresses_[4].getRecipient(bs::XBTAmount(120 * ccLotSize_))); signer.addRecipient(userCCAddresses_[5].getRecipient(bs::XBTAmount(50 * ccLotSize_))); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(70 * COIN)))); - signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(uint64_t(30 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(70 * COIN)))); + signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(int64_t(30 * COIN)))); auto unsignedTxRaw = signer.serializeUnsignedTx(); Tx txUnsigned(unsignedTxRaw); @@ -3544,8 +3548,8 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addRecipient(userCCAddresses_[4].getRecipient(bs::XBTAmount(210 * ccLotSize_))); signer.addRecipient(userCCAddresses_[5].getRecipient(bs::XBTAmount(70 * ccLotSize_))); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(70 * COIN)))); - signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(uint64_t(20 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(70 * COIN)))); + signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(int64_t(20 * COIN)))); auto unsignedTxRaw = signer.serializeUnsignedTx(); Tx txUnsigned(unsignedTxRaw); @@ -3595,8 +3599,8 @@ TEST_F(TestCCoin, TxCandidateParsing) signer.addSpender(std::make_shared(xbtUtxos1.back())); signer.addSpender(std::make_shared(xbtUtxos2.back())); - signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(uint64_t(70 * COIN)))); - signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(uint64_t(20 * COIN)))); + signer.addRecipient(userFundAddresses_[6].getRecipient(bs::XBTAmount(int64_t(70 * COIN)))); + signer.addRecipient(userFundAddresses_[7].getRecipient(bs::XBTAmount(int64_t(20 * COIN)))); signer.addRecipient(userCCAddresses_[3].getRecipient(bs::XBTAmount(100 * ccLotSize_))); signer.addRecipient(userCCAddresses_[4].getRecipient(bs::XBTAmount(120 * ccLotSize_))); @@ -3646,6 +3650,7 @@ TEST_F(TestCCoin, processZC_whileMined) // While obtaining ZCs in processZcBatch, one of the transactions got mined ASSERT_TRUE(false) << "Forcefully fails - to be implemented (see comments in source code)"; } +#endif //0 //TODO: //over assign cc diff --git a/UnitTests/TestCCoin.h b/UnitTests/TestCCoin.h index f130bc978..cb31ba1f3 100644 --- a/UnitTests/TestCCoin.h +++ b/UnitTests/TestCCoin.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -18,8 +18,9 @@ #include "Address.h" #include "BlockchainMonitor.h" +//#include "ColoredCoinLogic.h" +#include "Wallets/SignContainer.h" #include "TestEnv.h" -#include "ColoredCoinLogic.h" namespace bs { namespace core { @@ -40,6 +41,7 @@ namespace bs { class WalletsManager; } } +class QtHCT; struct CCoinSpender { @@ -51,6 +53,7 @@ struct CCoinSpender uint64_t xbtValue_ = 0; }; +#if 0 // no CC tests since all CC code is turned off class ColoredCoinTestACT : public ColoredCoinACT { private: @@ -149,12 +152,12 @@ class ColoredCoinTrackerClient_UT : public ColoredCoinTrackerClient } }; -class TestCCoin : public ::testing::Test +class TestCCoin : public ::testing::Test, public SignerCallbackTarget { public: using UTXOs = std::vector; - const uint64_t ccLotSize_ = 307; + const int64_t ccLotSize_ = 307; size_t usersCount_ = 20; @@ -165,6 +168,7 @@ class TestCCoin : public ::testing::Test std::shared_ptr syncMgr_; std::vector localACTs_; + std::shared_ptr hct_; bs::Address genesisAddr_; bs::Address revocationAddr_; @@ -225,5 +229,6 @@ class TestCCoin : public ::testing::Test void zcUpdate(std::shared_ptr); void reorg(std::shared_ptr); }; +#endif #endif // __TEST_CCOIN_H__ diff --git a/UnitTests/TestCCoinAsync.cpp b/UnitTests/TestCCoinAsync.cpp index a13b2c9fe..902fb2f18 100644 --- a/UnitTests/TestCCoinAsync.cpp +++ b/UnitTests/TestCCoinAsync.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -15,13 +15,15 @@ #include "CheckRecipSigner.h" #include "CoreHDWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "Wallets/SyncHDLeaf.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" -using namespace ArmorySigner; +using namespace Armory::Signer; +#if 0 // CC code turned off TestCCoinAsync::TestCCoinAsync() {} @@ -73,7 +75,8 @@ void TestCCoinAsync::SetUp() } } - auto inprocSigner = std::make_shared(envPtr_->walletsMgr(), envPtr_->logger(), "", NetworkType::TestNet); + auto inprocSigner = std::make_shared(envPtr_->walletsMgr() + , envPtr_->logger(), this, "", NetworkType::TestNet); inprocSigner->Start(); syncMgr_ = std::make_shared(envPtr_->logger(), envPtr_->appSettings(), envPtr_->armoryConnection()); syncMgr_->setSignContainer(inprocSigner); @@ -244,7 +247,7 @@ BinaryData TestCCoinAsync::FundFromCoinbase( throw std::runtime_error("Not enough cb coins! Mine more blocks!"); for (auto && addr : addresses) { - signer.addRecipient(addr.getRecipient(bs::XBTAmount{ valuePerOne })); + signer.addRecipient(addr.getRecipient(bs::XBTAmount{ (int64_t)valuePerOne })); } signer.setFeed(coinbaseFeed_); @@ -275,7 +278,7 @@ BinaryData TestCCoinAsync::SimpleSendMany(const bs::Address & fromAddress, const { std::vector> recipients; for(const auto & addr : toAddresses) { - recipients.push_back(addr.getRecipient(bs::XBTAmount{ valuePerOne })); + recipients.push_back(addr.getRecipient(bs::XBTAmount{ (int64_t)valuePerOne })); } const uint64_t requiredValue = valuePerOne * toAddresses.size(); @@ -346,15 +349,15 @@ Tx TestCCoinAsync::CreateCJtx( } //CC recipients - cjSigner.addRecipient(structB.ccAddr_.getRecipient(bs::XBTAmount{ structB.ccValue_ })); + cjSigner.addRecipient(structB.ccAddr_.getRecipient(bs::XBTAmount{ (int64_t)structB.ccValue_ })); if(ccValue - structB.ccValue_ > 0) - cjSigner.addRecipient(structA.ccAddr_.getRecipient(bs::XBTAmount{ ccValue - structB.ccValue_ })); + cjSigner.addRecipient(structA.ccAddr_.getRecipient(bs::XBTAmount{ (int64_t)(ccValue - structB.ccValue_) })); //XBT recipients - cjSigner.addRecipient(structA.xbtAddr_.getRecipient(bs::XBTAmount{ structA.xbtValue_ })); + cjSigner.addRecipient(structA.xbtAddr_.getRecipient(bs::XBTAmount{ (int64_t)structA.xbtValue_ })); if(xbtValue - structA.xbtValue_ - fee > 0) - cjSigner.addRecipient(structB.xbtAddr_.getRecipient(bs::XBTAmount{ xbtValue - structA.xbtValue_ - fee })); + cjSigner.addRecipient(structB.xbtAddr_.getRecipient(bs::XBTAmount{ (int64_t)(xbtValue - structA.xbtValue_ - fee) })); { auto leaf = envPtr_->walletsMgr()->getHDRootForLeaf(sellerSignWallet->walletId()); @@ -1028,7 +1031,7 @@ TEST_F(TestCCoinAsync, ZeroConfChain) { Signer signer; signer.addSpender(spender); - signer.addRecipient(addr.getRecipient(bs::XBTAmount{ value })); + signer.addRecipient(addr.getRecipient(bs::XBTAmount{ (int64_t)value })); auto script = spender->getOutputScript(); auto changeAddr = BtcUtils::getScrAddrForScript(script); @@ -1172,7 +1175,7 @@ TEST_F(TestCCoinAsync, Reorg) for (auto& recipient : recipients) { total += recipient.second; - signer.addRecipient(recipient.first.getRecipient(bs::XBTAmount{ recipient.second })); + signer.addRecipient(recipient.first.getRecipient(bs::XBTAmount{ (int64_t)recipient.second })); } if (total > spender->getValue()) @@ -1472,3 +1475,4 @@ TEST_F(TestCCoinAsync, Reorg) EXPECT_EQ(cct->getCcValueForAddress(userCCAddresses_[c]), y*ccLotSize_); } } +#endif //0 diff --git a/UnitTests/TestCCoinAsync.h b/UnitTests/TestCCoinAsync.h index 4d362c2b7..dc3ffe137 100644 --- a/UnitTests/TestCCoinAsync.h +++ b/UnitTests/TestCCoinAsync.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -18,8 +18,8 @@ #include "Address.h" #include "BlockchainMonitor.h" +#include "Wallets/SignContainer.h" #include "TestEnv.h" -#include "CCLogicAsync.h" namespace bs { namespace core { @@ -40,8 +40,9 @@ namespace bs { class WalletsManager; } } +class QtHCT; - +#if 0 // CC code turned off class AsyncCCT : public ColoredCoinTrackerAsync { public: @@ -68,7 +69,7 @@ class AsyncCCT : public ColoredCoinTrackerAsync }; -class TestCCoinAsync : public ::testing::Test +class TestCCoinAsync : public ::testing::Test, public SignerCallbackTarget { public: using UTXOs = std::vector; @@ -83,6 +84,7 @@ class TestCCoinAsync : public ::testing::Test std::vector> userWallets_; std::shared_ptr syncMgr_; + std::shared_ptr hct_; bs::Address genesisAddr_; bs::Address revocationAddr_; @@ -176,5 +178,5 @@ class AsyncCCT_ACT : public SingleUTWalletACT private: std::map> refreshCb_; }; - +#endif //0 #endif // TEST_CCOIN_ASYNC_H diff --git a/UnitTests/TestCommon.cpp b/UnitTests/TestCommon.cpp index f7290bd61..61f09ff62 100644 --- a/UnitTests/TestCommon.cpp +++ b/UnitTests/TestCommon.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -28,9 +28,11 @@ #include "CacheFile.h" #include "CurrencyPair.h" #include "EasyCoDec.h" -#include "InprocSigner.h" -#include "MarketDataProvider.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "MDCallbacksQt.h" +#include "MarketDataProvider.h" +#include "PriceAmount.h" #include "TestEnv.h" #include "WalletUtils.h" #include "Wallets/SyncWalletsManager.h" @@ -96,7 +98,9 @@ TEST(TestCommon, AssetManager) TestEnv env(StaticLogger::loggerPtr); env.requireAssets(); - auto inprocSigner = std::make_shared(env.walletsMgr(), StaticLogger::loggerPtr, "", NetworkType::TestNet); + auto hct = new QtHCT(nullptr); + auto inprocSigner = std::make_shared(env.walletsMgr() + , StaticLogger::loggerPtr, hct, "", NetworkType::TestNet); inprocSigner->Start(); auto syncMgr = std::make_shared(StaticLogger::loggerPtr , env.appSettings(), env.armoryConnection()); @@ -104,7 +108,7 @@ TEST(TestCommon, AssetManager) syncMgr->syncWallets(); const auto &mdCallbacks = env.mdCallbacks(); - AssetManager assetMgr(StaticLogger::loggerPtr, syncMgr, mdCallbacks, env.celerConnection()); + AssetManager assetMgr(StaticLogger::loggerPtr, nullptr); //FIXME: pass AssetMgrCT as a second arg, if needed assetMgr.connect(mdCallbacks.get(), &MDCallbacksQt::MDSecurityReceived, &assetMgr, &AssetManager::onMDSecurityReceived); assetMgr.connect(mdCallbacks.get(), &MDCallbacksQt::MDSecuritiesReceived, &assetMgr, &AssetManager::onMDSecuritiesReceived); @@ -134,6 +138,7 @@ TEST(TestCommon, AssetManager) assetMgr.onMDUpdate(bs::network::Asset::PrivateMarket, QLatin1String("BLK/XBT") , { bs::network::MDField{bs::network::MDField::PriceLast, 0.023} }); EXPECT_EQ(assetMgr.getPrice("BLK"), 0.023); + delete hct; } TEST(TestCommon, UtxoReservation) @@ -315,6 +320,8 @@ TEST(TestCommon, BotanSerpent) } #include "AssetEncryption.h" +using namespace Armory::Wallets::Encryption; + TEST(TestCommon, BotanSerpent_KDF_Romix) { Botan::AutoSeeded_RNG rng; @@ -391,6 +398,30 @@ TEST(TestCommon, XBTAmount) auto xbt1 = bs::XBTAmount(double(21*1000*1000)); // Check that converting to double and back some big amount dooes not loose minimum difference (1 satoshi) // This will also check +/- operators - auto diff1 = bs::XBTAmount((xbt1 + bs::XBTAmount(uint64_t(1))).GetValueBitcoin()) - xbt1; + auto diff1 = bs::XBTAmount((xbt1 + bs::XBTAmount(int64_t(1))).GetValueBitcoin()) - xbt1; EXPECT_EQ(diff1, 1); } + +TEST(TestCommon, PriceAmount) +{ + EXPECT_EQ(bs::CentAmount(0.0).to_string(), "0.00"); + EXPECT_EQ(bs::CentAmount(0.1).to_string(), "0.10"); + EXPECT_EQ(bs::CentAmount(-0.1).to_string(), "-0.10"); + EXPECT_EQ(bs::CentAmount(0.19).to_string(), "0.19"); + EXPECT_EQ(bs::CentAmount(-0.19).to_string(), "-0.19"); + + EXPECT_EQ(bs::CentAmount(0.129).to_string(), "0.12"); + EXPECT_EQ(bs::CentAmount(0.1299999).to_string(), "0.12"); + EXPECT_EQ(bs::CentAmount(0.13).to_string(), "0.13"); + EXPECT_EQ(bs::CentAmount(-0.129).to_string(), "-0.12"); + EXPECT_EQ(bs::CentAmount(-0.1299999).to_string(), "-0.12"); + EXPECT_EQ(bs::CentAmount(-0.13).to_string(), "-0.13"); + + EXPECT_EQ(bs::CentAmount(-0.0001).to_string(), "0.00"); + EXPECT_EQ(bs::CentAmount(0.0001).to_string(), "0.00"); + + EXPECT_EQ(bs::CentAmount(12345.0001).to_string(), "12345.00"); + EXPECT_EQ(bs::CentAmount(-12345.0001).to_string(), "-12345.00"); + EXPECT_EQ(bs::CentAmount(0.12345).to_string(), "0.12"); + EXPECT_EQ(bs::CentAmount(-0.12345).to_string(), "0.12"); +} diff --git a/UnitTests/TestEnv.cpp b/UnitTests/TestEnv.cpp index 6cebe7b4e..675de4822 100644 --- a/UnitTests/TestEnv.cpp +++ b/UnitTests/TestEnv.cpp @@ -12,15 +12,14 @@ #include "TestEnv.h" #include "ApplicationSettings.h" -#include "ArmoryObject.h" +#include "ArmoryConfig.h" +#include "AuthorizedPeers.h" +#include "Wallets/ArmoryObject.h" #include "ArmorySettings.h" -#include "AuthAddressManager.h" -#include "CelerClient.h" #include "ConnectionManager.h" #include "CoreWalletsManager.h" #include "MarketDataProvider.h" #include "MDCallbacksQt.h" -#include "QuoteProvider.h" #include "SystemFileUtils.h" #include "UiUtils.h" @@ -31,6 +30,9 @@ #include #include +using namespace Armory::Signer; +using namespace Armory::Wallets; + const BinaryData testnetGenesisBlock = READHEX("0100000000000000000000000000000000000\ 000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51\ 323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae18010100000001000000000000000000000000000\ @@ -41,7 +43,7 @@ e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0f 112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"); std::shared_ptr StaticLogger::loggerPtr = nullptr; -ArmoryThreading::TimedQueue> ACTqueue::notifQueue_; +Armory::Threading::TimedQueue> ACTqueue::notifQueue_; TestEnv::TestEnv(const std::shared_ptr &logger) { @@ -75,8 +77,6 @@ void TestEnv::shutdown() } mdProvider_ = nullptr; - quoteProvider_ = nullptr; - celerConn_ = nullptr; assetMgr_ = nullptr; connMgr_ = nullptr; @@ -84,20 +84,21 @@ void TestEnv::shutdown() walletsMgr_ = nullptr; - armoryInstance_ = nullptr; - armoryConnection_ = nullptr; + armoryInstance_.reset(); + armoryConnection_.reset(); QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).removeRecursively(); ACTqueue::notifQueue_.clear(); } -void TestEnv::requireArmory() +void TestEnv::requireArmory(bool waitForReady) { //init armorydb - if (armoryInstance_ != nullptr) + if (armoryInstance_ != nullptr) { + armoryInstance_->init(); return; - + } armoryInstance_ = std::make_shared(); auto armoryConnection = std::make_shared( @@ -113,7 +114,7 @@ void TestEnv::requireArmory() auto keyCb = [](const BinaryData&, const std::string&)->bool { return true; - }; + }; armoryConnection->setupConnection(settings, keyCb); armoryConnection_ = armoryConnection; @@ -121,14 +122,21 @@ void TestEnv::requireArmory() qDebug() << "Waiting for ArmoryDB connection..."; while (armoryConnection_->state() != ArmoryState::Connected) { - QThread::msleep(1); + std::this_thread::sleep_for(std::chrono::milliseconds{ 1 }); + } + if (waitForReady) { + qDebug() << "Armory connected - waiting for ready state..."; + } + else { + qDebug() << "Armory connected - go online"; } - qDebug() << "Armory connected - waiting for ready state..."; armoryConnection_->goOnline(); - while (armoryConnection_->state() != ArmoryState::Ready) { - QThread::msleep(1); + if (waitForReady) { + while (armoryConnection_->state() != ArmoryState::Ready) { + std::this_thread::sleep_for(std::chrono::milliseconds{ 1 }); + } + logger_->debug("Armory is ready - continue execution"); } - logger_->debug("Armory is ready - continue execution"); } void TestEnv::requireAssets() @@ -140,7 +148,6 @@ void TestEnv::requireAssets() mdCallbacks_ = std::make_shared(); mdProvider_ = std::make_shared(logger_, mdCallbacks_.get()); - quoteProvider_ = std::make_shared(assetMgr_, logger_); } void TestEnv::requireConnections() @@ -148,12 +155,16 @@ void TestEnv::requireConnections() requireArmory(); connMgr_ = std::make_shared(logger_); - celerConn_ = std::make_shared(connMgr_); } /////////////////////////////////////////////////////////////////////////////// ArmoryInstance::ArmoryInstance() : blkdir_("./blkfiletest"), homedir_("./fakehomedir"), ldbdir_("./ldbtestdir") +{ + init(); +} + +void ArmoryInstance::init() { //setup armory folders SystemFileUtils::rmDir(blkdir_); @@ -165,11 +176,9 @@ ArmoryInstance::ArmoryInstance() SystemFileUtils::mkPath(ldbdir_); //setup env - NetworkConfig::selectNetwork(NETWORK_MODE_TESTNET); - BlockDataManagerConfig::setServiceType(SERVICE_WEBSOCKET); - BlockDataManagerConfig::setDbType(ARMORY_DB_SUPER); - BlockDataManagerConfig::setOperationMode(OPERATION_UNITTEST); - auto& magicBytes = NetworkConfig::getMagicBytes(); + Armory::Config::NetworkSettings::selectNetwork(Armory::Config::NETWORK_MODE_TESTNET); + Armory::Config::DBSettings::setServiceType(SERVICE_WEBSOCKET); + auto& magicBytes = Armory::Config::BitcoinSettings::getMagicBytes(); //create block file with testnet genesis block auto blk0dat = BtcUtils::getBlkFilename(blkdir_, 0); @@ -187,21 +196,17 @@ ArmoryInstance::ArmoryInstance() fs.close(); //setup config - config_.blkFileLocation_ = blkdir_; - config_.dbDir_ = ldbdir_; - config_.threadCount_ = 3; - config_.dataDir_ = homedir_; - config_.ephemeralPeers_ = false; - port_ = UNITTEST_DB_PORT; - std::stringstream port_ss; - port_ss << port_; - config_.listenPort_ = port_ss.str(); //setup bip151 context startupBIP150CTX(4); - const auto lbdEmptyPassphrase = [](const std::set &) { + std::vector args{ "--db-type=DB_SUPER", "--public" + , "--satoshi-datadir=" + blkdir_, "--dbdir=" + ldbdir_, "--datadir=" + homedir_ + , "--thread-count=3", "--listen-port=" + std::to_string(port_) }; + Armory::Config::parseArgs(args, Armory::Config::ProcessType::DB); + + const auto lbdEmptyPassphrase = [](const std::set&) { return SecureBinaryData{}; }; @@ -213,50 +218,57 @@ ArmoryInstance::ArmoryInstance() auto& clientPubkey = clientPeers.getOwnPublicKey(); std::stringstream serverAddr; - serverAddr << "127.0.0.1:" << config_.listenPort_; + serverAddr << "127.0.0.1:" << port_; clientPeers.addPeer(serverPubkey, serverAddr.str()); serverPeers.addPeer(clientPubkey, "127.0.0.1"); - //init bdm - nodePtr_ = - std::make_shared(*(unsigned int*)magicBytes.getPtr(), false); - const auto watchNode = std::make_shared(0, true); - config_.bitcoinNodes_ = { nodePtr_, watchNode }; - config_.rpcNode_ = std::make_shared(nodePtr_, watchNode); + //init bdm & network nodes + nodePtr_ = std::dynamic_pointer_cast( + Armory::Config::NetworkSettings::bitcoinNodes().first); - theBDMt_ = new BlockDataManagerThread(config_); + nodePtr_ = std::dynamic_pointer_cast( + Armory::Config::NetworkSettings::bitcoinNodes().first); + + rpcNode_ = std::dynamic_pointer_cast( + Armory::Config::NetworkSettings::rpcNode()); + + theBDMt_ = new BlockDataManagerThread(); iface_ = theBDMt_->bdm()->getIFace(); - auto nodePtr = std::dynamic_pointer_cast(nodePtr_); - nodePtr->setBlockchain(theBDMt_->bdm()->blockchain()); - nodePtr->setBlockFiles(theBDMt_->bdm()->blockFiles()); + nodePtr_->setBlockchain(theBDMt_->bdm()->blockchain()); + nodePtr_->setBlockFiles(theBDMt_->bdm()->blockFiles()); nodePtr_->setIface(iface_); - theBDMt_->start(config_.initMode_); + theBDMt_->start(INIT_RESUME); - WebSocketServer::initAuthPeers(lbdEmptyPassphrase); //start server + WebSocketServer::initAuthPeers(lbdEmptyPassphrase); WebSocketServer::start(theBDMt_, true); } //// -ArmoryInstance::~ArmoryInstance() +void ArmoryInstance::shutdown() { + if (theBDMt_ == nullptr) { + return; + } + //shutdown server - auto&& bdvObj2 = AsyncClient::BlockDataViewer::getNewBDV("127.0.0.1" - , config_.listenPort_, BlockDataManagerConfig::getDataDir() - , [](const std::set &) { return SecureBinaryData{}; } - , BlockDataManagerConfig::ephemeralPeers_, true, nullptr); + auto&& bdvObj2 = AsyncClient::BlockDataViewer::getNewBDV( + "127.0.0.1", std::to_string(port_), Armory::Config::getDataDir() + , [](const std::set&) { return SecureBinaryData{}; } + , Armory::Config::NetworkSettings::ephemeralPeers(), true, nullptr); auto&& serverPubkey = WebSocketServer::getPublicKey(); bdvObj2->addPublicKey(serverPubkey); bdvObj2->connectToRemote(); - bdvObj2->shutdown(config_.cookie_); + bdvObj2->shutdown(Armory::Config::NetworkSettings::cookie()); WebSocketServer::waitOnShutdown(); //shutdown bdm delete theBDMt_; theBDMt_ = nullptr; + nodePtr_ = nullptr; //clean up dirs SystemFileUtils::rmDir(blkdir_); @@ -265,7 +277,7 @@ ArmoryInstance::~ArmoryInstance() } std::map ArmoryInstance::mineNewBlock( - ArmorySigner::ScriptRecipient* rec, unsigned count) + ScriptRecipient* rec, unsigned count) { return nodePtr_->mineNewBlock(theBDMt_->bdm(), count, rec); } @@ -280,21 +292,31 @@ void ArmoryInstance::pushZC(const BinaryData& zc, unsigned int blocksUntilMined, void ArmoryInstance::setReorgBranchPoint(const BinaryData& hash) { auto headerPtr = theBDMt_->bdm()->blockchain()->getHeaderByHash(hash); - if (headerPtr == nullptr) + if (headerPtr == nullptr) { throw std::runtime_error("null header ptr"); - + } nodePtr_->setReorgBranchPoint(headerPtr); } BinaryData ArmoryInstance::getCurrentTopBlockHash() const { auto headerPtr = theBDMt_->bdm()->blockchain()->top(); - if (headerPtr == nullptr) + if (headerPtr == nullptr) { throw std::runtime_error("null header ptr"); - + } return headerPtr->getThisHash(); } +uint32_t ArmoryInstance::getCurrentTopBlock(void) const +{ + auto headerPtr = theBDMt_->bdm()->blockchain()->top(); + if (headerPtr == nullptr) { + throw std::runtime_error("null header ptr"); + } + return headerPtr->getBlockHeight(); +} + + //// SingleUTWalletACT::~SingleUTWalletACT() { @@ -352,7 +374,7 @@ std::vector UnitTestWalletACT::waitOnZC(bool soft) } return notif->zc_; } - catch (const ArmoryThreading::StackTimedOutException &) { + catch (const Armory::Threading::StackTimedOutException &) { return {}; } } @@ -370,7 +392,7 @@ int UnitTestWalletACT::waitOnBroadcastError(const std::string &reqId) } return notif->errCode_; } - catch (const ArmoryThreading::StackTimedOutException &) { + catch (const Armory::Threading::StackTimedOutException &) { return 0; } } diff --git a/UnitTests/TestEnv.h b/UnitTests/TestEnv.h index d7ce4cc64..74424054f 100644 --- a/UnitTests/TestEnv.h +++ b/UnitTests/TestEnv.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -11,11 +11,10 @@ #ifndef __TEST_ENV_H__ #define __TEST_ENV_H__ -#include "ArmoryObject.h" -#include "AuthAddressLogic.h" +#include "ArmoryConfig.h" +#include "Wallets/ArmoryObject.h" #include "BDM_mainthread.h" #include "BlockchainMonitor.h" -#include "BlockDataManagerConfig.h" #include "gtest/NodeUnitTest.h" #include "MockAssetMgr.h" #include "Server.h" @@ -30,6 +29,8 @@ #include #include +using namespace Armory::Signer; + #define UNITTEST_DB_PORT 59095 struct StaticLogger @@ -46,7 +47,7 @@ class ApplicationSettings; class ArmoryConnection; class AuthAddressManager; class BlockchainMonitor; -class CelerClient; +class CelerClientQt; class ConnectionManager; class MarketDataProvider; class MDCallbacksQt; @@ -56,7 +57,7 @@ namespace ArmorySigner { class BIP32_AssetPath; }; -class ResolverOneAddress : public ArmorySigner::ResolverFeed +class ResolverOneAddress : public Armory::Signer::ResolverFeed { private: SecureBinaryData privKey_; @@ -87,18 +88,11 @@ class ResolverOneAddress : public ArmorySigner::ResolverFeed return privKey_; } - void setBip32PathForPubkey(const BinaryData &, const ArmorySigner::BIP32_AssetPath&) override - { - throw std::runtime_error("not implemented"); - } - - ArmorySigner::BIP32_AssetPath resolveBip32PathForPubkey(const BinaryData&) override - { - throw std::runtime_error("not implemented"); - } + void setBip32PathForPubkey(const BinaryData&, const BIP32_AssetPath&) override {} + BIP32_AssetPath resolveBip32PathForPubkey(const BinaryData&) override { return { {}, {}, {}, nullptr }; } }; -class ResolverManyAddresses : public ArmorySigner::ResolverFeed +class ResolverManyAddresses : public Armory::Signer::ResolverFeed { private: std::map hashToPubKey_; @@ -134,20 +128,13 @@ class ResolverManyAddresses : public ArmorySigner::ResolverFeed return iter->second; } - void setBip32PathForPubkey(const BinaryData &, const ArmorySigner::BIP32_AssetPath&) override - { - throw std::runtime_error("not implemented"); - } - - ArmorySigner::BIP32_AssetPath resolveBip32PathForPubkey(const BinaryData&) override - { - throw std::runtime_error("not implemented"); - } + void setBip32PathForPubkey(const BinaryData&, const BIP32_AssetPath&) override {} + BIP32_AssetPath resolveBip32PathForPubkey(const BinaryData&) override { return { {}, {}, {}, nullptr }; } }; struct ACTqueue { - static ArmoryThreading::TimedQueue> notifQueue_; + static Armory::Threading::TimedQueue> notifQueue_; }; class SingleUTWalletACT : public ArmoryCallbackTarget @@ -282,7 +269,7 @@ class UnitTestWalletACT : public bs::sync::WalletACT struct UnitTestLocalACT : public bs::sync::WalletACT { - ArmoryThreading::BlockingQueue> notifQueue_; + Armory::Threading::BlockingQueue> notifQueue_; public: UnitTestLocalACT(ArmoryConnection *armory, bs::sync::Wallet *leaf) : @@ -401,21 +388,24 @@ struct ArmoryInstance const std::string ldbdir_; int port_; - std::shared_ptr nodePtr_; - - BlockDataManagerConfig config_; + std::shared_ptr nodePtr_; + std::shared_ptr rpcNode_; BlockDataManagerThread* theBDMt_; LMDBBlockDatabase* iface_; ArmoryInstance(); - ~ArmoryInstance(void); + ~ArmoryInstance(void) { shutdown(); } - std::map mineNewBlock(ArmorySigner::ScriptRecipient*, unsigned); + std::map mineNewBlock(Armory::Signer::ScriptRecipient*, unsigned); void pushZC(const BinaryData &, unsigned int blocksUntilMined = 0, bool stage = false); void setReorgBranchPoint(const BinaryData&); BinaryData getCurrentTopBlockHash(void) const; + uint32_t getCurrentTopBlock(void) const; + + void init(); + void shutdown(); }; class TestArmoryConnection : public ArmoryObject @@ -456,32 +446,28 @@ class TestEnv void shutdown(void); - std::shared_ptr appSettings() { return appSettings_; } + [[deprecated]] std::shared_ptr appSettings() { return appSettings_; } std::shared_ptr armoryConnection() { return armoryConnection_; } std::shared_ptr armoryInstance() { return armoryInstance_; } - std::shared_ptr assetMgr() { return assetMgr_; } + [[deprecated]] std::shared_ptr assetMgr() { return assetMgr_; } std::shared_ptr blockMonitor() { return blockMonitor_; } - std::shared_ptr connectionMgr() { return connMgr_; } - std::shared_ptr celerConnection() { return celerConn_; } + [[deprecated]] std::shared_ptr connectionMgr() { return connMgr_; } std::shared_ptr logger() { return logger_; } std::shared_ptr walletsMgr() { return walletsMgr_; } - std::shared_ptr mdProvider() { return mdProvider_; } - std::shared_ptr mdCallbacks() { return mdCallbacks_; } - std::shared_ptr quoteProvider() { return quoteProvider_; } + [[deprecated]] std::shared_ptr mdProvider() { return mdProvider_; } + [[deprecated]] std::shared_ptr mdCallbacks() { return mdCallbacks_; } - void requireArmory(); - void requireAssets(); - void requireConnections(); + void requireArmory(bool waitForReady = true); + [[deprecated]] void requireAssets(); + [[deprecated]] void requireConnections(); private: std::shared_ptr appSettings_; std::shared_ptr assetMgr_; std::shared_ptr blockMonitor_; - std::shared_ptr celerConn_; std::shared_ptr connMgr_; std::shared_ptr mdCallbacks_; std::shared_ptr mdProvider_; - std::shared_ptr quoteProvider_; std::shared_ptr walletsMgr_; std::shared_ptr logger_; std::shared_ptr armoryConnection_; diff --git a/UnitTests/TestNetwork.cpp b/UnitTests/TestNetwork.cpp index ab8cfe8c1..38f2585a1 100644 --- a/UnitTests/TestNetwork.cpp +++ b/UnitTests/TestNetwork.cpp @@ -13,10 +13,8 @@ #include "Bip15xDataConnection.h" #include "Bip15xServerConnection.h" -#include "CelerMessageMapper.h" #include "CommonTypes.h" #include "IdStringGenerator.h" -#include "QuoteProvider.h" #include "ServerConnection.h" #include "TestEnv.h" #include "TransportBIP15x.h" @@ -60,6 +58,7 @@ static bs::network::TransportBIP15xServer::TrustedClientsCallback constructTrust }; } +#if 0 TEST(TestNetwork, CelerMessageMapper) { for (int i = CelerAPI::CelerMessageTypeFirst; i < CelerAPI::CelerMessageTypeLast; i++) { @@ -76,16 +75,17 @@ using namespace bs::network; TEST(TestNetwork, Types) { for (const auto t : {Side::Buy, Side::Sell}) { - const auto celerType = Side::toCeler(t); - EXPECT_EQ(Side::fromCeler(celerType), t) << "Side " << Side::toString(t); + const auto celerType = bs::celer::toCeler(t); + EXPECT_EQ(bs::celer::fromCeler(celerType), t) << "Side " << Side::toString(t); } for (int i = bs::network::Asset::first; i < bs::network::Asset::last; i++) { const auto t = static_cast(i); - const auto celerProdType = bs::network::Asset::toCelerProductType(t); - EXPECT_EQ(bs::network::Asset::fromCelerProductType(celerProdType), t) << "Asset type " << bs::network::Asset::toString(t); + const auto celerProdType = bs::celer::toCelerProductType(t); + EXPECT_EQ(bs::celer::fromCelerProductType(celerProdType), t) << "Asset type " << bs::network::Asset::toString(t); } } +#endif //0 TEST(TestNetwork, IdString) { diff --git a/UnitTests/TestOtc.cpp b/UnitTests/TestOtc.cpp index fe05f0f67..86770a8c8 100644 --- a/UnitTests/TestOtc.cpp +++ b/UnitTests/TestOtc.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -12,13 +12,14 @@ #include "CoreHDWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "SettableField.h" #include "StringUtils.h" #include "TestEnv.h" -#include "TradesUtils.h" +#include "Wallets/TradesUtils.h" #include "TradesVerification.h" -#include "Trading/OtcClient.h" +//#include "Trading/OtcClient.h" #include "Wallets/SyncHDWallet.h" #include "Wallets/SyncWalletsManager.h" @@ -34,8 +35,9 @@ namespace { const auto kSettlementId = std::string("dc26c004d7b24f71cd5b348a254c292777586f5d9d00f60ac65dd7d5b06d0c2b"); } // namespace +class QtHCT; -class TestPeer +class TestPeer : public SignerCallbackTarget { public: void init(TestEnv &env, const std::string &name) @@ -79,7 +81,8 @@ class TestPeer walletsMgr_->addWallet(wallet_); - signer_ = std::make_shared(walletsMgr_, env.logger(), "", NetworkType::TestNet); + signer_ = std::make_shared(walletsMgr_, env.logger(), this + , "", NetworkType::TestNet); signer_->Start(); syncWalletMgr_ = std::make_shared(env.logger() @@ -100,10 +103,11 @@ class TestPeer } const auto regIDs = syncWalletMgr_->registerWallets(); UnitTestWalletACT::waitOnRefresh(regIDs); - +#if 0 OtcClientParams params; otc_ = std::make_shared(env.logger(), syncWalletMgr_, env.armoryConnection(), signer_, nullptr, nullptr, env.appSettings(), params); otc_->setOwnContactId(name); +#endif } std::string name_; @@ -111,7 +115,8 @@ class TestPeer std::shared_ptr walletsMgr_; std::shared_ptr syncWalletMgr_; std::shared_ptr signer_; - std::shared_ptr otc_; + std::shared_ptr hct_; + //std::shared_ptr otc_; bs::Address authAddress_; bs::Address nativeAddr_; bs::Address nestedAddr_; @@ -139,7 +144,7 @@ class TestOtc : public ::testing::Test auto d = response.mutable_start_otc(); d->set_request_id(request.start_otc().request_id()); d->set_settlement_id(kSettlementId); - peer.otc_->processPbMessage(response); + //peer.otc_->processPbMessage(response); break; } @@ -242,7 +247,7 @@ class TestOtc : public ::testing::Test ASSERT_TRUE(false) << std::to_string(request.data_case()); } }; - +#if 0 QObject::connect(peer1_.otc_.get(), &OtcClient::sendContactMessage, qApp, [this](const std::string &contactId, const BinaryData &data) { peer2_.otc_->processContactMessage(peer1_.name_, data); }, Qt::QueuedConnection); @@ -256,6 +261,7 @@ class TestOtc : public ::testing::Test QObject::connect(peer2_.otc_.get(), &OtcClient::sendPbMessage, qApp, [this, processPbMessage](const std::string &data) { processPbMessage(peer2_, data); }, Qt::QueuedConnection); +#endif //0 } void sendStateUpdate(ProxyTerminalPb::OtcState state) @@ -265,14 +271,14 @@ class TestOtc : public ::testing::Test d->set_settlement_id(kSettlementId); d->set_state(state); d->set_timestamp_ms(QDateTime::currentDateTime().toMSecsSinceEpoch()); - peer1_.otc_->processPbMessage(response); - peer2_.otc_->processPbMessage(response); + //peer1_.otc_->processPbMessage(response); + //peer2_.otc_->processPbMessage(response); } void mineNewBlocks(const bs::Address &dst, unsigned count) { auto curHeight = env_->armoryConnection()->topBlock(); - auto addrRecip = dst.getRecipient(bs::XBTAmount{uint64_t(1 * COIN)}); + auto addrRecip = dst.getRecipient(bs::XBTAmount{int64_t(1 * COIN)}); env_->armoryInstance()->mineNewBlock(addrRecip.get(), count); env_->blockMonitor()->waitForNewBlocks(curHeight + count); } @@ -292,8 +298,8 @@ class TestOtc : public ::testing::Test manualInput_ = testNum & 0x0004; withoutChange_ = testNum & 0x0008; - peer1_.otc_->contactConnected(peer2_.name_); - peer2_.otc_->contactConnected(peer1_.name_); + //peer1_.otc_->contactConnected(peer2_.name_); + //peer2_.otc_->contactConnected(peer1_.name_); auto &sender = sellerOffers_ ? peer1_ : peer2_; auto &receiver = sellerOffers_ ? peer2_ : peer1_; @@ -323,7 +329,7 @@ class TestOtc : public ::testing::Test // needed to be able sign pay-in and pay-out const bs::core::WalletPasswordScoped lock1(peer1_.wallet_, kPassword); const bs::core::WalletPasswordScoped lock2(peer2_.wallet_, kPassword); - +#if 0 { bs::network::otc::Offer offer; offer.price = 100; @@ -366,6 +372,7 @@ class TestOtc : public ::testing::Test ASSERT_TRUE(payoutDone_); ASSERT_TRUE(payinSealDone_); ASSERT_TRUE(payinDone_); +#endif //0 } std::unique_ptr env_; diff --git a/UnitTests/TestSettlement.cpp b/UnitTests/TestSettlement.cpp index bc45cf12b..82e0925af 100644 --- a/UnitTests/TestSettlement.cpp +++ b/UnitTests/TestSettlement.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -9,34 +9,37 @@ */ #include "TestSettlement.h" -#include -#include -#include -#include -#include #include -#include "ApplicationSettings.h" #include "CoreHDWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" +#include "MessageUtils.h" +#include "MockTerminal.h" +#include "TestAdapters.h" #include "TestEnv.h" -#include "TransactionData.h" -#include "Wallets/SyncWalletsManager.h" -#include "Wallets/SyncHDWallet.h" -#include "Wallets/SyncPlainWallet.h" -#include "CheckRecipSigner.h" -using std::make_unique; -using namespace std::chrono_literals; -using namespace ArmorySigner; +#include "common.pb.h" +#include "terminal.pb.h" -void TestSettlement::mineBlocks(unsigned count) +using namespace Armory::Signer; +using namespace bs::message; +using namespace BlockSettle::Common; +using namespace BlockSettle::Terminal; + +constexpr auto kFutureWaitTimeout = std::chrono::seconds(5); +constexpr auto kLongWaitTimeout = std::chrono::seconds(15); + +void TestSettlement::mineBlocks(unsigned count, bool wait) { - auto curHeight = envPtr_->armoryConnection()->topBlock(); +// auto curHeight = envPtr_->armoryConnection()->topBlock(); + auto curHeight = envPtr_->armoryInstance()->getCurrentTopBlock(); Recipient_P2PKH coinbaseRecipient(coinbaseScrAddr_, 50 * COIN); auto&& cbMap = envPtr_->armoryInstance()->mineNewBlock(&coinbaseRecipient, count); coinbaseHashes_.insert(cbMap.begin(), cbMap.end()); - envPtr_->blockMonitor()->waitForNewBlocks(curHeight + count); + if (wait) { // don't use if armoryConnection is not ready + envPtr_->blockMonitor()->waitForNewBlocks(curHeight + count); + } } void TestSettlement::sendTo(uint64_t value, bs::Address& addr) @@ -59,7 +62,7 @@ void TestSettlement::sendTo(uint64_t value, bs::Address& addr) signer.addSpender(spendPtr); - signer.addRecipient(addr.getRecipient(bs::XBTAmount{ value })); + signer.addRecipient(addr.getRecipient(bs::XBTAmount{ (int64_t)value })); signer.setFeed(coinbaseFeed_); //sign & send @@ -68,9 +71,6 @@ void TestSettlement::sendTo(uint64_t value, bs::Address& addr) } TestSettlement::TestSettlement() -{} - -void TestSettlement::SetUp() { passphrase_ = SecureBinaryData::fromString("pass"); coinbasePubKey_ = CryptoECDSA().ComputePublicKey(coinbasePrivKey_, true); @@ -79,18 +79,20 @@ void TestSettlement::SetUp() std::make_shared(coinbasePrivKey_, coinbasePubKey_); envPtr_ = std::make_shared(StaticLogger::loggerPtr); - envPtr_->requireAssets(); -// act_ = std::make_unique(envPtr_->armoryConnection().get()); + envPtr_->requireArmory(false); - mineBlocks(101); + mineBlocks(101, false); +} +void TestSettlement::SetUp() +{ const auto logger = envPtr_->logger(); - const auto amount = (initialTransferAmount_ + 10) * COIN; + const auto amount = initialTransferAmount_ * COIN; - walletsMgr_ = std::make_shared(logger); const bs::wallet::PasswordData pd{ passphrase_, { bs::wallet::EncryptionType::Password } }; for (size_t i = 0; i < nbParties_; i++) { + walletsMgr_.push_back(std::make_shared(logger)); auto hdWallet = std::make_shared( "Primary" + std::to_string(i), "" , NetworkType::TestNet, pd @@ -102,11 +104,14 @@ void TestSettlement::SetUp() { const bs::core::WalletPasswordScoped lock(hdWallet, passphrase_); leaf = grp->createLeaf(AddressEntryType_P2SH, 0); - addr = leaf->getNewExtAddress(); } + addr = leaf->getNewExtAddress(); sendTo(amount, addr); + recvAddrs_.push_back(leaf->getNewExtAddress()); + changeAddrs_.push_back(leaf->getNewIntAddress()); + std::shared_ptr authLeaf, settlLeaf; bs::Address authAddr; SecureBinaryData authKey; @@ -120,7 +125,7 @@ void TestSettlement::SetUp() settlLeaf = hdWallet->createSettlementLeaf(authAddr); const auto assetPtr = settlLeaf->getRootAsset(); - const auto assetSingle = std::dynamic_pointer_cast(assetPtr); + const auto assetSingle = std::dynamic_pointer_cast(assetPtr); if (assetSingle) { authKey = assetSingle->getPubKey()->getCompressedKey(); } @@ -128,7 +133,7 @@ void TestSettlement::SetUp() logger->debug("[TestSettlement] {} fundAddr={}, authAddr={}, authKey={}" , hdWallet->name(), addr.display(), authAddr.display(), authKey.toHexStr()); - walletsMgr_->addWallet(hdWallet); + walletsMgr_[i]->addWallet(hdWallet); xbtWallet_.emplace_back(leaf); authWallet_.push_back(authLeaf); fundAddrs_.emplace_back(addr); @@ -136,54 +141,15 @@ void TestSettlement::SetUp() authAddrs_.emplace_back(authAddr); authKeys_.emplace_back(std::move(authKey)); hdWallet_.push_back(hdWallet); - } - auto inprocSigner = std::make_shared( - walletsMgr_, logger, "", NetworkType::TestNet); - inprocSigner->Start(); - syncMgr_ = std::make_shared(logger - , envPtr_->appSettings(), envPtr_->armoryConnection()); - syncMgr_->setSignContainer(inprocSigner); - auto promSync = std::make_shared>(); - auto futSync = promSync->get_future(); - syncMgr_->syncWallets([promSync](int cur, int total) { - if (cur == total) { - promSync->set_value(true); - } - }); - futSync.wait(); - - UnitTestWalletACT::clear(); - - for (const auto &hdWallet : syncMgr_->hdWallets()) { - hdWallet->setCustomACT(envPtr_->armoryConnection()); + inprocSigner_.push_back(std::make_shared( + walletsMgr_.at(i), logger, this, "", NetworkType::TestNet + , [this, hdWallet](const std::string&) { + return std::make_unique(hdWallet, passphrase_); + })); + inprocSigner_.at(i)->Start(); } - - const auto regIDs = syncMgr_->registerWallets(); - UnitTestWalletACT::waitOnRefresh(regIDs); - -// auto curHeight = envPtr_->armoryConnection()->topBlock(); mineBlocks(6); - - auto promPtr = std::make_shared>(); - auto fut = promPtr->get_future(); - auto ctrPtr = std::make_shared>(0); - auto wltCount = syncMgr_->getAllWallets().size() - 2; - - auto balLBD = [promPtr, ctrPtr, wltCount](void)->void - { - ctrPtr->fetch_add(1); - if (*ctrPtr == wltCount) - promPtr->set_value(true); - }; - - for (const auto &wallet : syncMgr_->getAllWallets()) { - wallet->updateBalances(balLBD); - } - - settlementId_ = CryptoPRNG::generateRandom(32); - - fut.wait(); } void TestSettlement::TearDown() @@ -193,337 +159,631 @@ void TestSettlement::TearDown() authAddrs_.clear(); fundAddrs_.clear(); hdWallet_.clear(); - userId_.clear(); - syncMgr_.reset(); + walletsMgr_.clear(); + inprocSigner_.clear(); } TestSettlement::~TestSettlement() -{} +{ + envPtr_->armoryInstance()->shutdown(); +} TEST_F(TestSettlement, Initial_balances) { ASSERT_FALSE(xbtWallet_.empty()); + ASSERT_EQ(nbParties_, 2); for (size_t i = 0; i < nbParties_; i++) { - ASSERT_NE(xbtWallet_[i], nullptr); - const auto syncWallet = syncMgr_->getWalletById(xbtWallet_[i]->walletId()); - ASSERT_NE(syncWallet, nullptr); - ASSERT_GE(syncWallet->getSpendableBalance(), initialTransferAmount_); + ASSERT_NE(xbtWallet_.at(i), nullptr); } + MockTerminal t1(StaticLogger::loggerPtr, "T1", inprocSigner_.at(0), envPtr_->armoryConnection()); + MockTerminal t2(StaticLogger::loggerPtr, "T2", inprocSigner_.at(1), envPtr_->armoryConnection()); + const auto& sup1 = std::make_shared(t1.name()); + t1.bus()->addAdapter(sup1); + const auto& sup2 = std::make_shared(t2.name()); + t2.bus()->addAdapter(sup2); + + const auto& walletReady = [](const auto& walletId) + { + return [walletId](const bs::message::Envelope& env) + { + if (env.isRequest() || + (env.sender->value() != TerminalUsers::Wallets)) { + return false; + } + WalletsMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == WalletsMessage::kWalletReady) { + return (msg.wallet_ready() == walletId); + } + } + return false; + }; + }; + auto fut1 = sup1->waitFor(walletReady(xbtWallet_.at(0)->walletId())); + auto fut2 = sup2->waitFor(walletReady(xbtWallet_.at(1)->walletId())); + t1.start(); + t2.start(); + ASSERT_EQ(fut1.wait_for(kFutureWaitTimeout), std::future_status::ready); + ASSERT_EQ(fut2.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const auto& walletBalance = [](const std::string& walletId, double expectedBal) + { + return [walletId, expectedBal](const bs::message::Envelope& env) + { + if (!env.receiver || (env.receiver->value() != TerminalUsers::API) + || (env.sender->value() != TerminalUsers::Wallets)) { + return false; + } + WalletsMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == WalletsMessage::kWalletBalances) { + return ((msg.wallet_balances().wallet_id() == walletId) + && qFuzzyCompare(msg.wallet_balances().spendable_balance(), expectedBal)); + } + } + return false; + }; + }; + fut1 = sup1->waitFor(walletBalance(xbtWallet_.at(0)->walletId() + , initialTransferAmount_)); + fut2 = sup2->waitFor(walletBalance(xbtWallet_.at(1)->walletId() + , initialTransferAmount_)); + WalletsMessage msgWlt; + msgWlt.set_get_wallet_balances(xbtWallet_.at(0)->walletId()); + sup1->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + msgWlt.set_get_wallet_balances(xbtWallet_.at(1)->walletId()); + sup2->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + ASSERT_EQ(fut1.wait_for(kFutureWaitTimeout), std::future_status::ready); + ASSERT_EQ(fut2.wait_for(kFutureWaitTimeout), std::future_status::ready); +} + +TEST_F(TestSettlement, SpotFX_sell) +{ + ASSERT_GE(inprocSigner_.size(), 2); + const std::string& email1 = "aaa@example.com"; + const std::string& email2 = "bbb@example.com"; + MockTerminal t1(StaticLogger::loggerPtr, "T1", inprocSigner_.at(0), envPtr_->armoryConnection()); + MockTerminal t2(StaticLogger::loggerPtr, "T2", inprocSigner_.at(1), envPtr_->armoryConnection()); + const auto& sup1 = std::make_shared(t1.name()); + t1.bus()->addAdapter(sup1); + const auto& sup2 = std::make_shared(t2.name()); + t2.bus()->addAdapter(sup2); + const auto& m1 = std::make_shared(StaticLogger::loggerPtr, "T1" + , email1, envPtr_->armoryInstance()); + const auto& m2 = std::make_shared(StaticLogger::loggerPtr, "T2" + , email2, envPtr_->armoryInstance()); + m1->link(m2); + m2->link(m1); + t1.bus()->addAdapter(m1); + t2.bus()->addAdapter(m2); + t1.start(); + t2.start(); + + const auto& rfqId = CryptoPRNG::generateRandom(5).toHexStr(); + const double qty = 123; + const auto& quoteReqNotif = [this, qty, rfqId](const Envelope& env) + { + if (env.receiver || (env.receiver && !env.receiver->isBroadcast()) || + (env.sender->value() != TerminalUsers::Settlement)) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == SettlementMessage::kQuoteReqNotif) { + const auto& rfq = msg.quote_req_notif().rfq(); + return ((rfq.security() == fxSecurity_) && (rfq.product() == fxProduct_) + && !rfq.buy() && (rfq.quantity() == qty) && (rfq.id() == rfqId)); + } + } + return false; + }; + auto fut = sup2->waitFor(quoteReqNotif); + + SettlementMessage msgSettl; + auto msgSendRFQ = msgSettl.mutable_send_rfq(); + auto msgRFQ = msgSendRFQ->mutable_rfq(); + msgRFQ->set_id(rfqId); + msgRFQ->set_security(fxSecurity_); + msgRFQ->set_product(fxProduct_); + msgRFQ->set_asset_type((int)bs::network::Asset::SpotFX); + msgRFQ->set_buy(false); + msgRFQ->set_quantity(qty); + sup1->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const double replyPrice = 1.23; + const auto& quoteReply = [this, replyPrice, qty, rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kQuote)) { + return ((msg.quote().security() == fxSecurity_) && (msg.quote().product() == fxProduct_) + && (msg.quote().request_id() == rfqId) && (msg.quote().price() == replyPrice) + && (msg.quote().quantity() == qty) && msg.quote().buy()); + } + return false; + }; + SettlementMessage inMsg; + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& qrn = fromMsg(inMsg.quote_req_notif()); + bs::network::QuoteNotification qn(qrn, {}, replyPrice, {}); + qn.validityInS = 5; + toMsg(qn, msgSettl.mutable_reply_to_rfq()); + fut = sup1->waitFor(quoteReply); + sup2->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quote = fromMsg(inMsg.quote()); + const auto& pendingOrder = [this, rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kPendingSettlement)) { + return (msg.pending_settlement().ids().rfq_id() == rfqId); + } + return false; + }; + fut = sup1->waitFor(pendingOrder); + ASSERT_EQ(fut.wait_for(kLongWaitTimeout), std::future_status::ready); + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quoteId = inMsg.pending_settlement().ids().quote_id(); + ASSERT_EQ(quoteId, quote.quoteId); - //auto promPtr = std::make_shared>(); - //auto fut = promPtr->get_future(); - // - //const auto &cbFee = [promPtr](float feePerByte) { - // promPtr->set_value(feePerByte); - //}; - //syncMgr_->estimatedFeePerByte(1, cbFee); + const auto& filledOrder = [this, rfqId, quoteId, replyPrice](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kMatchedQuote)) { + const auto& matched = msg.matched_quote(); + return ((matched.rfq_id() == rfqId) && (matched.quote_id() == quoteId) + && (matched.price() == replyPrice)); + } + return false; + }; + fut = sup2->waitFor(filledOrder); + auto msgAccept = msgSettl.mutable_accept_rfq(); + msgAccept->set_rfq_id(rfqId); + toMsg(quote, msgAccept->mutable_quote()); + sup1->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); +} - //fut.wait(); +TEST_F(TestSettlement, SpotFX_buy) +{ + ASSERT_GE(inprocSigner_.size(), 2); + const std::string& email1 = "aaa@example.com"; + const std::string& email2 = "bbb@example.com"; + MockTerminal t1(StaticLogger::loggerPtr, "T1", inprocSigner_.at(0), envPtr_->armoryConnection()); + MockTerminal t2(StaticLogger::loggerPtr, "T2", inprocSigner_.at(1), envPtr_->armoryConnection()); + const auto& sup1 = std::make_shared(t1.name()); + t1.bus()->addAdapter(sup1); + const auto& sup2 = std::make_shared(t2.name()); + t2.bus()->addAdapter(sup2); + const auto& m1 = std::make_shared(StaticLogger::loggerPtr, "T1" + , email1, envPtr_->armoryInstance()); + const auto& m2 = std::make_shared(StaticLogger::loggerPtr, "T2" + , email2, envPtr_->armoryInstance()); + m1->link(m2); + m2->link(m1); + t1.bus()->addAdapter(m1); + t2.bus()->addAdapter(m2); + t1.start(); + t2.start(); + + const auto& rfqId = CryptoPRNG::generateRandom(5).toHexStr(); + const double qty = 234; + const auto& quoteReqNotif = [this, qty, rfqId](const Envelope& env) + { + if (env.receiver || (env.receiver && !env.receiver->isBroadcast()) || + (env.sender->value() != TerminalUsers::Settlement)) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == SettlementMessage::kQuoteReqNotif) { + const auto& rfq = msg.quote_req_notif().rfq(); + return ((rfq.security() == fxSecurity_) && (rfq.product() == fxProduct_) + && rfq.buy() && (rfq.quantity() == qty) && (rfq.id() == rfqId)); + } + } + return false; + }; + auto fut = sup2->waitFor(quoteReqNotif); + + SettlementMessage msgSettl; + auto msgSendRFQ = msgSettl.mutable_send_rfq(); + auto msgRFQ = msgSendRFQ->mutable_rfq(); + msgRFQ->set_id(rfqId); + msgRFQ->set_security(fxSecurity_); + msgRFQ->set_product(fxProduct_); + msgRFQ->set_asset_type((int)bs::network::Asset::SpotFX); + msgRFQ->set_buy(true); + msgRFQ->set_quantity(qty); + sup1->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const double replyPrice = 1.21; + const auto& quoteReply = [this, replyPrice, qty, rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kQuote)) { + return ((msg.quote().security() == fxSecurity_) && (msg.quote().product() == fxProduct_) + && (msg.quote().request_id() == rfqId) && (msg.quote().price() == replyPrice) + && (msg.quote().quantity() == qty) && !msg.quote().buy()); + } + return false; + }; + SettlementMessage inMsg; + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& qrn = fromMsg(inMsg.quote_req_notif()); + bs::network::QuoteNotification qn(qrn, {}, replyPrice, {}); + qn.validityInS = 5; + toMsg(qn, msgSettl.mutable_reply_to_rfq()); + fut = sup1->waitFor(quoteReply); + sup2->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quote = fromMsg(inMsg.quote()); + const auto& pendingOrder = [this, rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kPendingSettlement)) { + return (msg.pending_settlement().ids().rfq_id() == rfqId); + } + return false; + }; + fut = sup1->waitFor(pendingOrder); + ASSERT_EQ(fut.wait_for(kLongWaitTimeout), std::future_status::ready); + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quoteId = inMsg.pending_settlement().ids().quote_id(); + ASSERT_EQ(quoteId, quote.quoteId); - //EXPECT_GE(fut.get(), 5); + const auto& filledOrder = [this, rfqId, quoteId, replyPrice](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kMatchedQuote)) { + const auto& matched = msg.matched_quote(); + return ((matched.rfq_id() == rfqId) && (matched.quote_id() == quoteId) + && (matched.price() == replyPrice)); + } + return false; + }; + fut = sup2->waitFor(filledOrder); + auto msgAccept = msgSettl.mutable_accept_rfq(); + msgAccept->set_rfq_id(rfqId); + toMsg(quote, msgAccept->mutable_quote()); + sup1->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); } -#if 0 //temporarily disabled TEST_F(TestSettlement, SpotXBT_sell) { - const auto feePerByte = TestEnv::walletsMgr()->estimatedFeePerByte(1); - const auto &settlWallet = TestEnv::walletsMgr()->GetSettlementWallet(); - uint32_t curHeight = 0; + ASSERT_FALSE(xbtWallet_.empty()); + ASSERT_GE(nbParties_, 2); + ASSERT_GE(inprocSigner_.size(), 2); + const float fpbRate = 2.3; + const std::string& email1 = "aaa@example.com"; + const std::string& email2 = "bbb@example.com"; + MockTerminal t1(StaticLogger::loggerPtr, "T1", inprocSigner_.at(0), envPtr_->armoryConnection()); + MockTerminal t2(StaticLogger::loggerPtr, "T2", inprocSigner_.at(1), envPtr_->armoryConnection()); + const auto& sup1 = std::make_shared(t1.name()); + t1.bus()->addAdapter(sup1); + const auto& sup2 = std::make_shared(t2.name()); + t2.bus()->addAdapter(sup2); + const auto& m1 = std::make_shared(StaticLogger::loggerPtr, "T1" + , email1, envPtr_->armoryInstance()); + const auto& m2 = std::make_shared(StaticLogger::loggerPtr, "T2" + , email2, envPtr_->armoryInstance()); + m1->link(m2); + m2->link(m1); + t1.bus()->addAdapter(m1); + t2.bus()->addAdapter(m2); + + const auto& walletReady = [](const auto& walletId) + { + return [walletId](const bs::message::Envelope& env) + { + if (env.isRequest() || env.receiver || + (env.sender->value() != TerminalUsers::Wallets)) { + return false; + } + WalletsMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == WalletsMessage::kWalletReady) { + return (msg.wallet_ready() == walletId); + } + } + return false; + }; + }; + auto fut1 = sup1->waitFor(walletReady(xbtWallet_.at(0)->walletId())); + auto fut2 = sup2->waitFor(walletReady(xbtWallet_.at(1)->walletId())); + t1.start(); + t2.start(); + + WalletsMessage msgWlt; + msgWlt.set_set_settlement_fee(fpbRate); + sup1->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + sup2->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + ASSERT_EQ(fut1.wait_for(kFutureWaitTimeout), std::future_status::ready); + ASSERT_EQ(fut2.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const auto& rfqId = CryptoPRNG::generateRandom(5).toHexStr(); + const double qty = 0.23; + + auto msgReq = msgWlt.mutable_reserve_utxos(); + msgReq->set_id(rfqId); + msgReq->set_sub_id(xbtWallet_.at(0)->walletId()); + msgReq->set_amount(bs::XBTAmount(qty).GetValue()); + sup1->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + + const auto& quoteReqNotif = [this, qty, rfqId](const Envelope& env) + { + if (env.receiver || (env.receiver && !env.receiver->isBroadcast()) || + (env.sender->value() != TerminalUsers::Settlement)) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == SettlementMessage::kQuoteReqNotif) { + const auto& rfq = msg.quote_req_notif().rfq(); + return ((rfq.security() == xbtSecurity_) && (rfq.product() == bs::network::XbtCurrency) + && !rfq.buy() && (rfq.quantity() == qty) && (rfq.id() == rfqId)); + } + } + return false; + }; + auto fut = sup2->waitFor(quoteReqNotif); + + SettlementMessage msgSettl; + auto msgSendRFQ = msgSettl.mutable_send_rfq(); + msgSendRFQ->set_reserve_id(rfqId); + auto msgRFQ = msgSendRFQ->mutable_rfq(); + msgRFQ->set_id(rfqId); + msgRFQ->set_security(xbtSecurity_); + msgRFQ->set_product(bs::network::XbtCurrency); + msgRFQ->set_asset_type((int)bs::network::Asset::SpotXBT); + msgRFQ->set_buy(false); + msgRFQ->set_quantity(qty); + msgRFQ->set_auth_pub_key(authKeys_.at(0).toHexStr()); + sup1->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const double replyPrice = 12345.67; + const auto& quoteReply = [this, replyPrice, qty, rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kQuote)) { + return ((msg.quote().security() == xbtSecurity_) && (msg.quote().product() == bs::network::XbtCurrency) + && (msg.quote().request_id() == rfqId) && (msg.quote().price() == replyPrice) + && (msg.quote().quantity() == qty) && msg.quote().buy()); + } + return false; + }; + SettlementMessage inMsg; + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& qrn = fromMsg(inMsg.quote_req_notif()); + bs::network::QuoteNotification qn(qrn, authKeys_.at(1).toHexStr(), replyPrice, {}); + qn.receiptAddress = recvAddrs_.at(1).display(); + qn.validityInS = 5; + toMsg(qn, msgSettl.mutable_reply_to_rfq()); + fut = sup1->waitFor(quoteReply); + sup2->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const auto& settlementId = BinaryData::CreateFromHex(qrn.settlementId); + ASSERT_FALSE(settlementId.empty()); + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quote = fromMsg(inMsg.quote()); + + const auto& pendingOrder = [rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kPendingSettlement)) { + return (msg.pending_settlement().ids().rfq_id() == rfqId); + } + return false; + }; + fut = sup1->waitFor(pendingOrder); + ASSERT_EQ(fut.wait_for(kLongWaitTimeout), std::future_status::ready); + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quoteId = inMsg.pending_settlement().ids().quote_id(); + ASSERT_EQ(quoteId, quote.quoteId); - for (int i = 0; i < nbParties_; i++) { - ASSERT_FALSE(authAddr_[i].isNull()); - ASSERT_FALSE(authWallet_[i]->GetPubChainedKeyFor(authAddr_[i]).isNull()); - } - const auto settlementAddr = settlWallet->newAddress(settlementId_ - , authWallet_[0]->GetPubChainedKeyFor(authAddr_[0]), authWallet_[1]->GetPubChainedKeyFor(authAddr_[1])); - ASSERT_NE(settlementAddr, nullptr); - EXPECT_TRUE(waitForSettlWallet()); - - // Requester's side - TransactionData reqTxData([] {}); - reqTxData.SetFeePerByte(feePerByte); - reqTxData.SetWallet(wallet_[1]); - const auto reqRecip = reqTxData.RegisterNewRecipient(); - reqTxData.UpdateRecipientAddress(reqRecip, settlementAddr); - reqTxData.UpdateRecipientAmount(reqRecip, 0.1); - ASSERT_TRUE(reqTxData.IsTransactionValid()); - ASSERT_GE(reqTxData.GetTransactionSummary().selectedBalance, 0.1); - - auto monitor = settlWallet->createMonitor(settlementAddr, TestEnv::logger()); - ASSERT_NE(monitor, nullptr); - connect(monitor.get(), &bs::SettlementMonitor::payInDetected, [this] { receivedPayIn_ = true; }); - connect(monitor.get(), &bs::SettlementMonitor::payOutDetected, [this] (int nbConf, bs::PayoutSigner::Type poType) { - qDebug() << "poType=" << poType << "nbConf=" << nbConf; - receivedPayOut_ = true; - poType_ = poType; - }); - monitor->start(); - - auto txPayInReq = reqTxData.CreateTXRequest(); - const auto txPayIn = reqTxData.GetWallet()->SignTXRequest(txPayInReq); - const auto txHex = QString::fromStdString(txPayIn.toHexStr()); - ASSERT_FALSE(txPayIn.isNull()); - ASSERT_TRUE(TestEnv::regtestControl()->SendTx(txHex)); - - ASSERT_TRUE(waitForPayIn()); - settlWallet->UpdateBalanceFromDB(); - EXPECT_DOUBLE_EQ(settlWallet->GetUnconfirmedBalance(), 0.1); - - // Responder's side - const auto authKeys = authWallet_[0]->GetKeyPairFor(authAddr_[0], {}); - ASSERT_FALSE(authKeys.privKey.isNull()); - ASSERT_FALSE(authKeys.pubKey.isNull()); - - ASSERT_FALSE(settlWallet->getSpendableZCList().empty()); - const auto payInHash = Tx(txPayIn).getThisHash(); - auto txReq = settlWallet->CreatePayoutTXRequest(settlWallet->GetInputFor(settlementAddr) - , fundAddr_[0], feePerByte); - ASSERT_FALSE(txReq.inputs.empty()); - const auto txPayOut = settlWallet->SignPayoutTXRequest(txReq, authKeys, settlementAddr->getAsset()->settlementId() - , settlementAddr->getAsset()->buyAuthPubKey(), settlementAddr->getAsset()->sellAuthPubKey()); - ASSERT_FALSE(txPayOut.isNull()); - ASSERT_TRUE(TestEnv::regtestControl()->SendTx(QString::fromStdString(txPayOut.toHexStr()))); - -// ASSERT_TRUE(waitForPayOut()); - curHeight = PyBlockDataManager::instance()->GetTopBlockHeight(); - TestEnv::regtestControl()->GenerateBlocks(1); - TestEnv::blockMonitor()->waitForNewBlocks(curHeight + 1); - EXPECT_TRUE(waitForPayOut()); - EXPECT_EQ(poType_, bs::PayoutSigner::Type::SignedByBuyer); - - curHeight = PyBlockDataManager::instance()->GetTopBlockHeight(); - TestEnv::regtestControl()->GenerateBlocks(6); - TestEnv::blockMonitor()->waitForNewBlocks(curHeight + 6); - settlWallet->UpdateBalanceFromDB(); - wallet_[0]->UpdateBalanceFromDB(); - wallet_[1]->UpdateBalanceFromDB(); - EXPECT_GT(wallet_[0]->GetTotalBalance(), initialTransferAmount_ + 0.1 - 0.01); // buyer (dealer) - EXPECT_LT(wallet_[1]->GetTotalBalance(), initialTransferAmount_ - 0.1); // seller (requester) - EXPECT_DOUBLE_EQ(settlWallet->GetTotalBalance(), 0); - monitor = nullptr; + const auto& settlementComplete = [rfqId, quoteId, settlementId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kSettlementComplete)) { + return ((msg.settlement_complete().rfq_id() == rfqId) && + (msg.settlement_complete().quote_id() == quoteId) && + (msg.settlement_complete().settlement_id() == settlementId.toBinStr())); + } + return false; + }; + fut = sup2->waitFor(settlementComplete); + ASSERT_EQ(fut.wait_for(kLongWaitTimeout), std::future_status::ready); } -#endif //0 -#if 0 TEST_F(TestSettlement, SpotXBT_buy) { - auto logger = StaticLogger::loggerPtr; - - const float feePerByte = 1.05; - ASSERT_GE(authAddrs_.size(), 2); - const auto settlWallet1 = std::dynamic_pointer_cast(settlLeafMap_[authAddrs_[0]]); - ASSERT_NE(settlWallet1, nullptr); - const auto settlWallet2 = std::dynamic_pointer_cast(settlLeafMap_[authAddrs_[1]]); - ASSERT_NE(settlWallet2, nullptr); - uint32_t curHeight = 0; - const double amount = 0.1; - - settlWallet1->addSettlementID(settlementId_); - settlWallet1->getNewExtAddress(); - settlWallet2->addSettlementID(settlementId_); - settlWallet2->getNewExtAddress(); - - ASSERT_GE(authKeys_.size(), 2); - ASSERT_FALSE(authKeys_[0].isNull()); - ASSERT_FALSE(authKeys_[1].isNull()); - - const bs::core::wallet::SettlementData settlDataBuy{ settlementId_, authKeys_[1], true }; - const bs::core::wallet::SettlementData settlDataSell{ settlementId_, authKeys_[0], false }; - const auto settlementAddr = hdWallet_[0]->getSettlementPayinAddress(settlDataBuy); - ASSERT_FALSE(settlementAddr.isNull()); - EXPECT_EQ(settlementAddr, hdWallet_[1]->getSettlementPayinAddress(settlDataSell)); - - const auto dummyWalletId = CryptoPRNG::generateRandom(8).toHexStr(); - auto syncSettlWallet = std::make_shared(dummyWalletId - , "temporary", "Dummy settlement wallet", nullptr, StaticLogger::loggerPtr); - syncSettlWallet->addAddress(settlementAddr, {}, false); - UnitTestWalletACT::clear(); - const auto settlRegId = syncSettlWallet->registerWallet(envPtr_->armoryConnection(), true); - UnitTestWalletACT::waitOnRefresh(settlRegId); - - const auto syncLeaf1 = syncMgr_->getWalletById(xbtWallet_[0]->walletId()); - const auto syncLeaf2 = syncMgr_->getWalletById(xbtWallet_[1]->walletId()); - - auto promUtxo2 = std::make_shared>(); - auto futUtxo2 = promUtxo2->get_future(); - const auto &cbTxOutList2 = [this, promUtxo2] - (const std::vector &inputs)->void + ASSERT_FALSE(xbtWallet_.empty()); + ASSERT_GE(nbParties_, 2); + ASSERT_GE(inprocSigner_.size(), 2); + const float fpbRate = 2.3; + const std::string& email1 = "aaa@example.com"; + const std::string& email2 = "bbb@example.com"; + MockTerminal t1(StaticLogger::loggerPtr, "T1", inprocSigner_.at(0), envPtr_->armoryConnection()); + MockTerminal t2(StaticLogger::loggerPtr, "T2", inprocSigner_.at(1), envPtr_->armoryConnection()); + const auto& sup1 = std::make_shared(t1.name()); + t1.bus()->addAdapter(sup1); + const auto& sup2 = std::make_shared(t2.name()); + t2.bus()->addAdapter(sup2); + const auto& m1 = std::make_shared(StaticLogger::loggerPtr, "T1" + , email1, envPtr_->armoryInstance()); + const auto& m2 = std::make_shared(StaticLogger::loggerPtr, "T2" + , email2, envPtr_->armoryInstance()); + m1->link(m2); + m2->link(m1); + t1.bus()->addAdapter(m1); + t2.bus()->addAdapter(m2); + + const auto& walletReady = [](const auto& walletId) + { + return [walletId](const bs::message::Envelope& env) + { + if (env.isRequest() || env.receiver || + (env.sender->value() != TerminalUsers::Wallets)) { + return false; + } + WalletsMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == WalletsMessage::kWalletReady) { + return (msg.wallet_ready() == walletId); + } + } + return false; + }; + }; + auto fut1 = sup1->waitFor(walletReady(xbtWallet_.at(0)->walletId())); + auto fut2 = sup2->waitFor(walletReady(xbtWallet_.at(1)->walletId())); + t1.start(); + t2.start(); + + WalletsMessage msgWlt; + msgWlt.set_set_settlement_fee(fpbRate); + sup1->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + sup2->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + ASSERT_EQ(fut1.wait_for(kFutureWaitTimeout), std::future_status::ready); + ASSERT_EQ(fut2.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const auto& rfqId = CryptoPRNG::generateRandom(5).toHexStr(); + const double qty = 0.237; + + const auto& quoteReqNotif = [this, qty, rfqId](const Envelope& env) { - if (inputs.size() != 1) { - promUtxo2->set_value({}); + if (env.receiver || (env.receiver && !env.receiver->isBroadcast()) || + (env.sender->value() != TerminalUsers::Settlement)) { + return false; } - else { - promUtxo2->set_value(inputs.front()); + SettlementMessage msg; + if (msg.ParseFromString(env.message)) { + if (msg.data_case() == SettlementMessage::kQuoteReqNotif) { + const auto& rfq = msg.quote_req_notif().rfq(); + return ((rfq.security() == xbtSecurity_) && (rfq.product() == bs::network::XbtCurrency) + && rfq.buy() && (rfq.quantity() == qty) && (rfq.id() == rfqId)); + } } + return false; }; - ASSERT_TRUE(syncLeaf2->getSpendableTxOutList(cbTxOutList2, UINT64_MAX, true)); - const auto input2 = futUtxo2.get(); - ASSERT_TRUE(input2.isInitialized()); - - std::vector payinInputs = { input2 }; - - // Dealer's side - TransactionData dealerTxData([] {}, envPtr_->logger()); - dealerTxData.setFeePerByte(feePerByte); - dealerTxData.setWalletAndInputs(syncLeaf2, payinInputs, envPtr_->armoryConnection()->topBlock()); - const auto dealerRecip = dealerTxData.RegisterNewRecipient(); - dealerTxData.UpdateRecipientAddress(dealerRecip, settlementAddr); - dealerTxData.UpdateRecipientAmount(dealerRecip, amount); - ASSERT_TRUE(dealerTxData.IsTransactionValid()); - ASSERT_GE(dealerTxData.GetTransactionSummary().selectedBalance, amount); - - bs::core::wallet::TXSignRequest unsignedTxReq; - if (dealerTxData.GetTransactionSummary().hasChange) { - const auto changeAddr = xbtWallet_[1]->getNewChangeAddress(); - unsignedTxReq = dealerTxData.createUnsignedTransaction(false, changeAddr); - } - else { - unsignedTxReq = dealerTxData.createUnsignedTransaction(); - } - ASSERT_TRUE(unsignedTxReq.isValid()); - - auto payinResolver = xbtWallet_[1]->getPublicResolver(); - const auto dealerPayInHash = unsignedTxReq.txId(payinResolver); - EXPECT_FALSE(dealerPayInHash.isNull()); - - const auto serializedPayinRequest = unsignedTxReq.serializeState(payinResolver); - - ASSERT_FALSE(serializedPayinRequest.isNull()); - - bs::CheckRecipSigner deserializedSigner{serializedPayinRequest , envPtr_->armoryConnection() }; - - auto inputs = deserializedSigner.getTxInsData(); - - auto spenders = deserializedSigner.spenders(); - auto recipients = deserializedSigner.recipients(); - - logger->debug("Settlement address: {}", settlementAddr.display()); - auto settlementAddressRecipient = settlementAddr.getRecipient(bs::XBTAmount{ amount }); - auto settlementRecipientScript = settlementAddressRecipient->getSerializedScript(); - - auto settAddressString = settlementAddr.display(); - - bool recipientFound = false; - - // amount to settlement address ( single output ) - for (const auto recipient : recipients) { - auto amount = recipient->getValue(); - auto serializedRecipient = recipient->getSerializedScript(); - - BtcUtils::pprintScript(serializedRecipient); - std::cout << '\n'; - - if (serializedRecipient == settlementRecipientScript) { - recipientFound = true; - break; + auto fut = sup2->waitFor(quoteReqNotif); + + SettlementMessage msgSettl; + auto msgSendRFQ = msgSettl.mutable_send_rfq(); + msgSendRFQ->set_reserve_id(rfqId); + auto msgRFQ = msgSendRFQ->mutable_rfq(); + msgRFQ->set_id(rfqId); + msgRFQ->set_security(xbtSecurity_); + msgRFQ->set_product(bs::network::XbtCurrency); + msgRFQ->set_asset_type((int)bs::network::Asset::SpotXBT); + msgRFQ->set_buy(true); + msgRFQ->set_quantity(qty); + msgRFQ->set_auth_pub_key(authKeys_.at(0).toHexStr()); + msgRFQ->set_receipt_address(recvAddrs_.at(0).display()); + sup1->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + auto msgReq = msgWlt.mutable_reserve_utxos(); + msgReq->set_id(rfqId); + msgReq->set_sub_id(xbtWallet_.at(1)->walletId()); + msgReq->set_amount(bs::XBTAmount(qty).GetValue()); + sup2->send(TerminalUsers::API, TerminalUsers::Wallets, msgWlt.SerializeAsString(), true); + const double replyPrice = 12345.78; + + const auto& quoteReply = [this, replyPrice, qty, rfqId](const Envelope& env) + { + if (env.sender->value() != TerminalUsers::Settlement) { + return false; } - } - - for (const auto recipient : recipients) { - logger->debug("{} : {}", bs::CheckRecipSigner::getRecipientAddress(recipient).display(), recipient->getValue()); - } - - // fee amount - // get all inputs amount - uint64_t totalInputAmount = 0; - for (const auto& input : spenders) { - totalInputAmount += input->getValue(); - } - - // get all outputs amount - uint64_t totalOutputValue = 0; - for (const auto& output : recipients) { - totalOutputValue += output->getValue(); - } - - try { - Tx tx{ serializedPayinRequest }; - - // XXX check that payin is not rbf - ASSERT_FALSE(tx.isRBF()); - - auto txOutCount = tx.getNumTxOut(); - - for (unsigned i = 0; i < txOutCount; ++i) { - auto txOut = tx.getTxOutCopy(i); - + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kQuote)) { + return ((msg.quote().security() == xbtSecurity_) && (msg.quote().product() == bs::network::XbtCurrency) + && (msg.quote().request_id() == rfqId) && (msg.quote().price() == replyPrice) + && (msg.quote().quantity() == qty) && !msg.quote().buy()); } - - auto txHash = tx.getThisHash(); - - StaticLogger::loggerPtr->debug("TX serialized"); - } catch (...) { - StaticLogger::loggerPtr->error("Failed to serialize TX"); - } - - // Requester's side - StaticLogger::loggerPtr->debug("[{}] payin hash: {}", __func__, dealerPayInHash.toHexStr(true)); - const auto payinInput = bs::SettlementMonitor::getInputFromTX(settlementAddr, dealerPayInHash - , bs::XBTAmount{ amount }); - const auto payoutTxReq = bs::SettlementMonitor::createPayoutTXRequest(payinInput - , fundAddrs_[0], feePerByte, envPtr_->armoryConnection()->topBlock()); - ASSERT_TRUE(payoutTxReq.isValid()); - - BinaryData txPayOut; + return false; + }; + SettlementMessage inMsg; + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& qrn = fromMsg(inMsg.quote_req_notif()); + bs::network::QuoteNotification qn(qrn, authKeys_.at(1).toHexStr(), replyPrice, {}); + //qn.receiptAddress = recvAddrs_.at(1).display(); + qn.validityInS = 5; + toMsg(qn, msgSettl.mutable_reply_to_rfq()); + fut = sup1->waitFor(quoteReply); + sup2->send(TerminalUsers::API, TerminalUsers::Settlement, msgSettl.SerializeAsString(), true); + ASSERT_EQ(fut.wait_for(kFutureWaitTimeout), std::future_status::ready); + + const auto& settlementId = BinaryData::CreateFromHex(qrn.settlementId); + ASSERT_FALSE(settlementId.empty()); + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quote = fromMsg(inMsg.quote()); + + const auto& pendingOrder = [rfqId](const Envelope& env) { - bs::core::WalletPasswordScoped passLock(hdWallet_[0], passphrase_); - txPayOut = hdWallet_[0]->signSettlementTXRequest(payoutTxReq, settlDataBuy); - } - ASSERT_FALSE(txPayOut.isNull()); + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kPendingSettlement)) { + return (msg.pending_settlement().ids().rfq_id() == rfqId); + } + return false; + }; + fut = sup1->waitFor(pendingOrder); + ASSERT_EQ(fut.wait_for(kLongWaitTimeout), std::future_status::ready); + ASSERT_TRUE(inMsg.ParseFromString(fut.get().message)); + const auto& quoteId = inMsg.pending_settlement().ids().quote_id(); + ASSERT_EQ(quoteId, quote.quoteId); - // Back to dealer - const auto payinTxReq = dealerTxData.getSignTxRequest(); - BinaryData txPayIn; - { - bs::core::WalletPasswordScoped passLock(hdWallet_[1], passphrase_); - txPayIn = xbtWallet_[1]->signTXRequest(payinTxReq); - } - ASSERT_FALSE(txPayIn.isNull()); - Tx txPayinObj(txPayIn); - EXPECT_EQ(txPayinObj.getThisHash(), dealerPayInHash); - - // get fee size. check against rate - const auto totalFee = totalInputAmount - totalOutputValue; - auto correctedFPB = feePerByte; - const auto estimatedFee = deserializedSigner.estimateFee(correctedFPB); - - UnitTestWalletACT::clear(); - StaticLogger::loggerPtr->debug("[{}] payin TX: {}", __func__, txPayIn.toHexStr()); - envPtr_->armoryInstance()->pushZC(txPayIn); - const auto& zcVecPayin = UnitTestWalletACT::waitOnZC(); - ASSERT_GE(zcVecPayin.size(), 1); - EXPECT_EQ(zcVecPayin[0].txHash, txPayinObj.getThisHash()); - - auto promUtxo = std::make_shared>(); - auto futUtxo = promUtxo->get_future(); - const auto &cbZCList = [this, promUtxo] - (const std::vector &inputs)->void + const auto& settlementComplete = [rfqId, quoteId, settlementId](const Envelope& env) { - if (inputs.size() != 1) { - promUtxo->set_value({}); - } else { - promUtxo->set_value(inputs.front()); + if (env.sender->value() != TerminalUsers::Settlement) { + return false; + } + SettlementMessage msg; + if (msg.ParseFromString(env.message) && (msg.data_case() == SettlementMessage::kSettlementComplete)) { + return ((msg.settlement_complete().rfq_id() == rfqId) && + (msg.settlement_complete().quote_id() == quoteId) && + (msg.settlement_complete().settlement_id() == settlementId.toBinStr())); } + return false; }; - ASSERT_TRUE(syncSettlWallet->getSpendableZCList(cbZCList)); - const auto zcPayin = futUtxo.get(); - ASSERT_TRUE(zcPayin.isInitialized()); - EXPECT_EQ(zcPayin, payinInput); - EXPECT_EQ(zcPayin.getScript(), payinInput.getScript()); - EXPECT_EQ(zcPayin.getTxOutIndex(), payinInput.getTxOutIndex()); - std::this_thread::sleep_for(20ms); - UnitTestWalletACT::clear(); - - Tx txPayoutObj(txPayOut); - ASSERT_TRUE(txPayoutObj.isInitialized()); - ASSERT_EQ(txPayoutObj.getNumTxIn(), 1); - ASSERT_EQ(txPayoutObj.getNumTxOut(), 1); - - StaticLogger::loggerPtr->debug("[{}] payout TX: {}", __func__, txPayOut.toHexStr()); - envPtr_->armoryInstance()->pushZC(txPayOut); - const auto& zcVecPayout = UnitTestWalletACT::waitOnZC(true); - ASSERT_GE(zcVecPayout.size(), 1); - EXPECT_EQ(zcVecPayout[0].txHash, txPayoutObj.getThisHash()); - std::this_thread::sleep_for(150ms); // have no idea yet, why it's required + fut = sup2->waitFor(settlementComplete); + ASSERT_EQ(fut.wait_for(kLongWaitTimeout), std::future_status::ready); } -#endif diff --git a/UnitTests/TestSettlement.h b/UnitTests/TestSettlement.h index 0d2b8d6a2..69e73ae88 100644 --- a/UnitTests/TestSettlement.h +++ b/UnitTests/TestSettlement.h @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -19,6 +19,7 @@ #include #include "Address.h" #include "BlockchainMonitor.h" +#include "Wallets/SignContainer.h" #include "TestEnv.h" @@ -41,8 +42,9 @@ namespace bs { class WalletsManager; } } +class QtHCT; -class TestSettlement : public ::testing::Test +class TestSettlement : public ::testing::Test, public SignerCallbackTarget { protected: TestSettlement(); @@ -51,11 +53,7 @@ class TestSettlement : public ::testing::Test void SetUp() override; void TearDown() override; - bool waitForPayIn() { return BlockchainMonitor::waitForFlag(receivedPayIn_); } - bool waitForPayOut() { return BlockchainMonitor::waitForFlag(receivedPayOut_); } -// bool waitForSettlWallet() { return BlockchainMonitor::waitForFlag(settlWalletReady_); } - - void mineBlocks(unsigned count); + void mineBlocks(unsigned count, bool wait = true); void sendTo(uint64_t value, bs::Address& addr); public: @@ -67,18 +65,17 @@ class TestSettlement : public ::testing::Test const double initialTransferAmount_ = 1.23; std::vector> hdWallet_; std::vector> authWallet_; - std::shared_ptr walletsMgr_; - std::shared_ptr syncMgr_; std::vector> xbtWallet_; + std::vector> walletsMgr_; + std::vector> inprocSigner_; std::vector authAddrs_; std::vector authKeys_; - std::vector fundAddrs_; - SecureBinaryData settlementId_; - std::vector userId_; + std::vector fundAddrs_, recvAddrs_, changeAddrs_; std::map> settlLeafMap_; - std::atomic_bool receivedPayIn_{ false }; - std::atomic_bool receivedPayOut_{ false }; - bs::PayoutSignatureType poType_{}; + + const std::string fxSecurity_{ "EUR/USD" }; + const std::string fxProduct_{ "EUR" }; + const std::string xbtSecurity_{ "XBT/EUR" }; private: QMutex mtxWalletId_; @@ -93,8 +90,6 @@ class TestSettlement : public ::testing::Test std::map coinbaseHashes_; unsigned coinbaseCounter_ = 0; - std::unique_ptr act_; - private: void onWalletReady(const QString &id); }; diff --git a/UnitTests/TestUi.cpp b/UnitTests/TestUi.cpp index 10f2a3b56..e3f759713 100644 --- a/UnitTests/TestUi.cpp +++ b/UnitTests/TestUi.cpp @@ -20,9 +20,7 @@ #include "CoreWalletsManager.h" #include "CustomControls/CustomDoubleSpinBox.h" #include "CustomControls/CustomDoubleValidator.h" -#include "InprocSigner.h" -#include "Trading/RequestingQuoteWidget.h" -#include "Trading/RFQTicketXBT.h" +#include "Wallets/InprocSigner.h" #include "TestEnv.h" #include "UiUtils.h" #include "Wallets/SyncHDWallet.h" diff --git a/UnitTests/TestWallet.cpp b/UnitTests/TestWallet.cpp index 1e0f710e5..cb3a4e3bc 100644 --- a/UnitTests/TestWallet.cpp +++ b/UnitTests/TestWallet.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -19,7 +19,8 @@ #include "CoreHDWallet.h" #include "CoreWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "SystemFileUtils.h" #include "TestEnv.h" #include "UiUtils.h" @@ -47,7 +48,7 @@ unit tests to add: ***/ //////////////////////////////////////////////////////////////////////////////// -class TestWallet : public ::testing::Test +class TestWallet : public ::testing::Test, public SignerCallbackTarget { void SetUp() { @@ -82,7 +83,7 @@ TEST_F(TestWallet, BIP84_derivation) { const bs::core::WalletPasswordScoped lock(wallet, passphrase); - wallet->createStructure(); + wallet->createStructure(false); ASSERT_NE(wallet->getGroup(wallet->getXBTGroupType()), nullptr); } @@ -189,8 +190,8 @@ TEST_F(TestWallet, BIP84_primary) EXPECT_EQ(leafCC->name(), "84'/16979'/7568'"); //16979 == 0x4253 } - auto inprocSigner = std::make_shared( - envPtr_->walletsMgr(), envPtr_->logger(), "", NetworkType::TestNet); + auto inprocSigner = std::make_shared(envPtr_->walletsMgr() + , envPtr_->logger(), this, "", NetworkType::TestNet); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -602,7 +603,7 @@ TEST_F(TestWallet, CreateDestroyLoad_SyncWallet) } //create sync manager - auto inprocSigner = std::make_shared(walletPtr, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -665,8 +666,9 @@ TEST_F(TestWallet, CreateDestroyLoad_SyncWallet) EXPECT_EQ(syncWallet->getUsedAddressCount(), 10); EXPECT_EQ(syncWallet->getExtAddressCount(), 5); EXPECT_EQ(syncWallet->getIntAddressCount(), 5); +#if 0 syncWallet->syncAddresses(); - +#endif //check address maps BIP32_Node extNode = base_node; extNode.derivePrivate(0); @@ -704,7 +706,7 @@ TEST_F(TestWallet, CreateDestroyLoad_SyncWallet) auto walletPtr = std::make_shared( filename, NetworkType::TestNet, "", ctrlPass, envPtr_->logger()); - auto inprocSigner = std::make_shared(walletPtr, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -789,8 +791,9 @@ TEST_F(TestWallet, CreateDestroyLoad_SyncWallet) EXPECT_EQ(syncWallet->getUsedAddressCount(), 12); EXPECT_EQ(syncWallet->getExtAddressCount(), 6); EXPECT_EQ(syncWallet->getIntAddressCount(), 6); +#if 0 syncWallet->syncAddresses(); - +#endif //create WO copy auto WOcopy = walletPtr->createWatchingOnly(); filename = WOcopy->getFileName(); @@ -807,7 +810,7 @@ TEST_F(TestWallet, CreateDestroyLoad_SyncWallet) EXPECT_EQ(walletPtr->isWatchingOnly(), true); //create sync manager - auto inprocSigner = std::make_shared(walletPtr, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1333,11 +1336,11 @@ TEST_F(TestWallet, SyncWallet_TriggerPoolExtension) { const bs::core::WalletPasswordScoped lock(walletPtr, passphrase); - walletPtr->createStructure(10); + walletPtr->createStructure(false, 10); } //create sync manager - auto inprocSigner = std::make_shared(walletPtr, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1693,7 +1696,7 @@ TEST_F(TestWallet, ImportExport_xpriv) wallet2->getDecryptedSeed()); ASSERT_TRUE(false); } - catch (const WalletException &) {} + catch (const Armory::Wallets::WalletException &) {} //shut it all down, reload, check seeds again filename = wallet2->getFileName(); @@ -1764,9 +1767,9 @@ TEST_F(TestWallet, TxIdNativeSegwit) UTXO input; input.unserialize(BinaryData::CreateFromHex( "cc16060000000000741618000300010020d5921cfa9b95c9fdafa9dca6d2765b5d7d2285914909b8f5f74f0b137259153b16001428d45f4ef82103691ea40c26b893a4566729b335ffffffff")); - request.armorySigner_.addSpender(std::make_shared(input)); + request.armorySigner_.addSpender(std::make_shared(input)); - auto recipient = ArmorySigner::ScriptRecipient::fromScript(BinaryData::CreateFromHex( + auto recipient = Armory::Signer::ScriptRecipient::fromScript(BinaryData::CreateFromHex( "a086010000000000220020aa38b39ed9b524967159ad2bd488d14c1b9ccd70364655a7d9f35cb83e4dc6ed")); request.armorySigner_.addRecipient(recipient); @@ -1801,7 +1804,7 @@ TEST_F(TestWallet, TxIdNestedSegwit) envPtr_->requireArmory(); ASSERT_NE(envPtr_->armoryConnection(), nullptr); - auto inprocSigner = std::make_shared(coreWallet, envPtr_->logger()); + auto inprocSigner = std::make_shared(coreWallet, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1821,10 +1824,11 @@ TEST_F(TestWallet, TxIdNestedSegwit) ASSERT_NE(syncHdWallet, nullptr); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 const auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); ASSERT_FALSE(regIDs.empty()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif auto syncWallet = syncMgr->getWalletById(coreLeaf->walletId()); auto syncLeaf = std::dynamic_pointer_cast(syncWallet); ASSERT_TRUE(syncLeaf != nullptr); @@ -1833,7 +1837,7 @@ TEST_F(TestWallet, TxIdNestedSegwit) unsigned blockCount = 6; auto curHeight = envPtr_->armoryConnection()->topBlock(); - auto addrRecip = address.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto addrRecip = address.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(addrRecip.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -1855,9 +1859,9 @@ TEST_F(TestWallet, TxIdNestedSegwit) ASSERT_TRUE(input.isInitialized()); bs::core::wallet::TXSignRequest request; - request.armorySigner_.addSpender(std::make_shared(input)); + request.armorySigner_.addSpender(std::make_shared(input)); - auto recipient = ArmorySigner::ScriptRecipient::fromScript(BinaryData::CreateFromHex( + auto recipient = Armory::Signer::ScriptRecipient::fromScript(BinaryData::CreateFromHex( "a086010000000000220020d35c94ed03ae988841bd990124e176dae3928ba41f5a684074a857e788d768ba")); request.armorySigner_.addRecipient(recipient); @@ -1936,7 +1940,6 @@ TEST_F(TestWallet, ChangeControlPassword) const bs::core::wallet::Seed seed{ SecureBinaryData::fromString("Sample test seed") , NetworkType::TestNet }; - { auto wallet = std::make_shared( walletName, walletDescr, seed, pd1, walletFolder_, envPtr_->logger()); diff --git a/UnitTests/TestWalletArmory.cpp b/UnitTests/TestWalletArmory.cpp index f22b3da15..8b1d21efd 100644 --- a/UnitTests/TestWalletArmory.cpp +++ b/UnitTests/TestWalletArmory.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2019 - 2020, BlockSettle AB +* Copyright (C) 2019 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -19,7 +19,8 @@ #include "CoreHDWallet.h" #include "CoreWallet.h" #include "CoreWalletsManager.h" -#include "InprocSigner.h" +#include "Wallets/HeadlessContainer.h" +#include "Wallets/InprocSigner.h" #include "SystemFileUtils.h" #include "TestEnv.h" #include "UiUtils.h" @@ -28,7 +29,7 @@ #include "Wallets/SyncWalletsManager.h" -class TestWalletWithArmory : public ::testing::Test +class TestWalletWithArmory : public ::testing::Test, public SignerCallbackTarget { protected: void SetUp() @@ -69,7 +70,7 @@ class TestWalletWithArmory : public ::testing::Test TEST_F(TestWalletWithArmory, AddressChainExtension) { - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -89,9 +90,10 @@ TEST_F(TestWalletWithArmory, AddressChainExtension) ASSERT_NE(syncHdWallet, nullptr); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif auto syncWallet = syncMgr->getWalletById(leafPtr_->walletId()); auto syncLeaf = std::dynamic_pointer_cast(syncWallet); ASSERT_TRUE(syncLeaf != nullptr); @@ -137,7 +139,7 @@ TEST_F(TestWalletWithArmory, AddressChainExtension) unsigned blockCount = 6; auto curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = addrVec[10].getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = addrVec[10].getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -149,7 +151,7 @@ TEST_F(TestWalletWithArmory, AddressChainExtension) ***/ curHeight = envPtr_->armoryConnection()->topBlock(); - recipient = addrVec[0].getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + recipient = addrVec[0].getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -189,7 +191,7 @@ TEST_F(TestWalletWithArmory, AddressChainExtension) const auto &cbTxOutList = [this, leaf=leafPtr_, syncLeaf, addrVec, promPtr1] (std::vector inputs)->void { - const auto recipient = addrVec[11].getRecipient(bs::XBTAmount{ (uint64_t)(25 * COIN) }); + const auto recipient = addrVec[11].getRecipient(bs::XBTAmount{ (int64_t)(25 * COIN) }); const auto txReq = syncLeaf->createTXRequest(inputs, { recipient }, true, 0, false, addrVec[0]); BinaryData txWrongSigned; { @@ -247,7 +249,7 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) std::vector intVec; { - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -268,8 +270,10 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) ASSERT_TRUE(syncLeaf != nullptr); syncLeaf->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncLeaf->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); +#endif //check wallet has 10 assets per account ASSERT_EQ(syncLeaf->getAddressPoolSize(), 20); @@ -320,7 +324,7 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) ASSERT_EQ(extVec.size(), 13); unsigned curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = extVec[12].getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = extVec[12].getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -367,7 +371,7 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) std::vector utxos; utxos.push_back(inputs[0]); - const auto recipient = extVec[13].getRecipient(bs::XBTAmount{ (uint64_t)(25 * COIN) }); + const auto recipient = extVec[13].getRecipient(bs::XBTAmount{ (int64_t)(25 * COIN) }); const auto txReq = syncLeaf->createTXRequest( utxos, { recipient }, true, 0, false, intVec[41]); @@ -453,7 +457,7 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) } //sync with db - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -473,9 +477,10 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) ASSERT_NE(syncHdWallet, nullptr); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif auto syncWallet = syncMgr->getWalletById(leafPtr_->walletId()); auto syncLeaf = std::dynamic_pointer_cast(syncWallet); ASSERT_TRUE(syncLeaf != nullptr); @@ -584,7 +589,7 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) filename, NetworkType::TestNet); //resync address chain use, it should not disrupt current state - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); syncMgr->setSignContainer(inprocSigner); @@ -604,9 +609,10 @@ TEST_F(TestWalletWithArmory, RestoreWallet_CheckChainLength) ASSERT_NE(syncHdWallet, nullptr); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif auto trackProm = std::make_shared>(); auto trackFut = trackProm->get_future(); auto trackLbd = [trackProm](bool result)->void @@ -673,7 +679,7 @@ TEST_F(TestWalletWithArmory, Comments) auto changeAddr = leafPtr_->getNewChangeAddress(); ASSERT_FALSE(changeAddr.empty()); - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -683,9 +689,10 @@ TEST_F(TestWalletWithArmory, Comments) auto syncHdWallet = syncMgr->getHDWalletById(walletPtr_->walletId()); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif auto syncWallet = syncMgr->getWalletById(leafPtr_->walletId()); ASSERT_EQ(syncWallet->getUsedAddressCount(), 2); EXPECT_EQ(syncWallet->getUsedAddressList()[0], addr); @@ -698,7 +705,7 @@ TEST_F(TestWalletWithArmory, Comments) unsigned blockCount = 6; const auto &curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = addr.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = addr.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -713,7 +720,7 @@ TEST_F(TestWalletWithArmory, Comments) EXPECT_TRUE(syncWallet->getSpendableTxOutList(cbTxOutList, UINT64_MAX, true)); const auto inputs = fut.get(); ASSERT_FALSE(inputs.empty()); - const auto recip = addr.getRecipient(bs::XBTAmount{ (uint64_t)12000 }); + const auto recip = addr.getRecipient(bs::XBTAmount{ (int64_t)12000 }); const auto txReq = syncWallet->createTXRequest(inputs, { recip }, true, 345 , false, changeAddr); @@ -737,7 +744,7 @@ TEST_F(TestWalletWithArmory, ZCBalance) const auto changeAddr = leafPtr_->getNewChangeAddress(); EXPECT_EQ(leafPtr_->getUsedAddressCount(), 3); - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -749,13 +756,14 @@ TEST_F(TestWalletWithArmory, ZCBalance) auto syncLeaf = syncMgr->getWalletById(leafPtr_->walletId()); syncWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); regIDs = syncWallet->setUnconfirmedTargets(); ASSERT_EQ(regIDs.size(), 2); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif //check balances are 0 auto balProm = std::make_shared>(); auto balFut = balProm->get_future(); @@ -774,7 +782,7 @@ TEST_F(TestWalletWithArmory, ZCBalance) unsigned blockCount = 6; auto curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = addr1.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = addr1.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -793,8 +801,8 @@ TEST_F(TestWalletWithArmory, ZCBalance) EXPECT_DOUBLE_EQ(syncLeaf->getUnconfirmedBalance(), 0); //spend these coins - const uint64_t amount = 5.0 * BTCNumericTypes::BalanceDivider; - const uint64_t fee = 0.0001 * BTCNumericTypes::BalanceDivider; + const int64_t amount = 5.0 * BTCNumericTypes::BalanceDivider; + const int64_t fee = 0.0001 * BTCNumericTypes::BalanceDivider; auto promPtr1 = std::make_shared>>(); auto fut1 = promPtr1->get_future(); @@ -930,7 +938,7 @@ TEST_F(TestWalletWithArmory, SimpleTX_bech32) const auto changeAddr = leafPtr_->getNewChangeAddress(); EXPECT_EQ(leafPtr_->getUsedAddressCount(), 4); - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -941,21 +949,22 @@ TEST_F(TestWalletWithArmory, SimpleTX_bech32) auto syncLeaf = syncMgr->getWalletById(leafPtr_->walletId()); syncWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif //mine some coins auto armoryInstance = envPtr_->armoryInstance(); unsigned blockCount = 6; auto curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = addr1.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = addr1.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); - const uint64_t amount1 = 0.05 * BTCNumericTypes::BalanceDivider; - const uint64_t fee = 0.0001 * BTCNumericTypes::BalanceDivider; + const int64_t amount1 = 0.05 * BTCNumericTypes::BalanceDivider; + const int64_t fee = 0.0001 * BTCNumericTypes::BalanceDivider; const auto &cbTX = [](bool result) { ASSERT_TRUE(result); @@ -1017,7 +1026,7 @@ TEST_F(TestWalletWithArmory, SimpleTX_bech32) const auto inputs2 = fut3.get(); ASSERT_FALSE(inputs2.empty()); - const uint64_t amount2 = 0.04 * BTCNumericTypes::BalanceDivider; + const int64_t amount2 = 0.04 * BTCNumericTypes::BalanceDivider; const auto recipient2 = addr3.getRecipient(bs::XBTAmount{ amount2 }); ASSERT_NE(recipient2, nullptr); const auto txReq2 = syncLeaf->createTXRequest( @@ -1037,6 +1046,7 @@ TEST_F(TestWalletWithArmory, SimpleTX_bech32) EXPECT_EQ(zcVec2[0].txHash, txObj2.getThisHash()); } +#if 0 TEST_F(TestWalletWithArmory, SignSettlement) { /*create settlement leaf*/ @@ -1074,7 +1084,7 @@ TEST_F(TestWalletWithArmory, SignSettlement) } /*sync the wallet and connect to db*/ - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1132,7 +1142,7 @@ TEST_F(TestWalletWithArmory, SignSettlement) unsigned blockCount = 6; auto curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = msAddress.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = msAddress.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); @@ -1155,8 +1165,8 @@ TEST_F(TestWalletWithArmory, SignSettlement) //create tx request const auto txReq = syncLeaf->createTXRequest(utxos, { - settlementRootAddress.getRecipient(bs::XBTAmount{ (uint64_t)(20 * COIN) }), - msAddress.getRecipient(bs::XBTAmount{(uint64_t)(30 * COIN)}) + settlementRootAddress.getRecipient(bs::XBTAmount{ (int64_t)(20 * COIN) }), + msAddress.getRecipient(bs::XBTAmount{(int64_t)(30 * COIN)}) }, 0, false); @@ -1183,10 +1193,11 @@ TEST_F(TestWalletWithArmory, SignSettlement) } } } +#endif //0 TEST_F(TestWalletWithArmory, GlobalDelegateConf) { - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1206,9 +1217,10 @@ TEST_F(TestWalletWithArmory, GlobalDelegateConf) ASSERT_NE(syncHdWallet, nullptr); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif auto syncWallet = syncMgr->getWalletById(leafPtr_->walletId()); auto syncLeaf = std::dynamic_pointer_cast(syncWallet); ASSERT_TRUE(syncLeaf != nullptr); @@ -1237,7 +1249,7 @@ TEST_F(TestWalletWithArmory, GlobalDelegateConf) const auto armoryInstance = envPtr_->armoryInstance(); auto curHeight = envPtr_->armoryConnection()->topBlock(); - const auto recipient = addr.getRecipient(bs::XBTAmount{ (uint64_t)(5 * COIN) }); + const auto recipient = addr.getRecipient(bs::XBTAmount{ (int64_t)(5 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), 6); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + 6, newTop); @@ -1269,7 +1281,7 @@ TEST_F(TestWalletWithArmory, GlobalDelegateConf) ASSERT_NE(globalLedger, nullptr); const auto &lbdGetLDEntries = [](const std::shared_ptr &ledger) - -> std::shared_ptr> + -> std::shared_ptr> { auto promLDPageCnt1 = std::make_shared>(); auto futLDPageCnt1 = promLDPageCnt1->get_future(); @@ -1285,11 +1297,11 @@ TEST_F(TestWalletWithArmory, GlobalDelegateConf) auto pageCnt1 = futLDPageCnt1.get(); EXPECT_GE(pageCnt1, 1); - auto ledgerEntries = std::make_shared>(); + auto ledgerEntries = std::make_shared>(); auto promLDEntries1 = std::make_shared>(); auto futLDEntries1 = promLDEntries1->get_future(); const auto &cbHistPage1 = [&pageCnt1, promLDEntries1, ledgerEntries] - (ReturnMessage> msg) + (ReturnMessage> msg) { try { const auto &entries = msg.get(); @@ -1328,7 +1340,7 @@ TEST_F(TestWalletWithArmory, GlobalDelegateConf) const auto &inputs = futTxOut.get(); ASSERT_FALSE(inputs.empty()); - const auto recip1 = addr1.getRecipient(bs::XBTAmount{ (uint64_t)(5 * COIN) }); + const auto recip1 = addr1.getRecipient(bs::XBTAmount{ (int64_t)(5 * COIN) }); const auto txReq = syncLeaf->createTXRequest(inputs, { recip1 }, true, 0, false, changeAddr); BinaryData txSigned; { @@ -1375,7 +1387,7 @@ TEST_F(TestWalletWithArmory, CallbackReturnTxCrash) auto addr = leafPtr_->getNewExtAddress(); ASSERT_FALSE(addr.empty()); - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1385,10 +1397,11 @@ TEST_F(TestWalletWithArmory, CallbackReturnTxCrash) auto syncHdWallet = syncMgr->getHDWalletById(walletPtr_->walletId()); syncHdWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncHdWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); - - auto recipient = addr.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); +#endif + auto recipient = addr.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); envPtr_->armoryInstance()->mineNewBlock(recipient.get(), 1); UnitTestWalletACT::waitOnNewBlock(); @@ -1415,7 +1428,7 @@ TEST_F(TestWalletWithArmory, PushZC_retry) prefixed.append(CryptoPRNG::generateRandom(20)); auto otherAddr = bs::Address::fromHash(prefixed); - auto inprocSigner = std::make_shared(walletPtr_, envPtr_->logger()); + auto inprocSigner = std::make_shared(walletPtr_, this, envPtr_->logger()); inprocSigner->Start(); auto syncMgr = std::make_shared(envPtr_->logger() , envPtr_->appSettings(), envPtr_->armoryConnection()); @@ -1427,26 +1440,27 @@ TEST_F(TestWalletWithArmory, PushZC_retry) auto syncLeaf = syncMgr->getWalletById(leafPtr_->walletId()); syncWallet->setCustomACT(envPtr_->armoryConnection()); +#if 0 auto regIDs = syncWallet->registerWallet(envPtr_->armoryConnection()); UnitTestWalletACT::waitOnRefresh(regIDs); regIDs = syncWallet->setUnconfirmedTargets(); ASSERT_EQ(regIDs.size(), 2); UnitTestWalletACT::waitOnRefresh(regIDs); - +#endif //mine some coins auto armoryInstance = envPtr_->armoryInstance(); unsigned blockCount = 6; auto curHeight = envPtr_->armoryConnection()->topBlock(); - auto recipient = addr1.getRecipient(bs::XBTAmount{ (uint64_t)(50 * COIN) }); + auto recipient = addr1.getRecipient(bs::XBTAmount{ (int64_t)(50 * COIN) }); armoryInstance->mineNewBlock(recipient.get(), blockCount); auto newTop = UnitTestWalletACT::waitOnNewBlock(); ASSERT_EQ(curHeight + blockCount, newTop); //spend these coins - const uint64_t amount = 5.0 * BTCNumericTypes::BalanceDivider; - const uint64_t fee = 0.0001 * BTCNumericTypes::BalanceDivider; + const int64_t amount = 5.0 * BTCNumericTypes::BalanceDivider; + const int64_t fee = 0.0001 * BTCNumericTypes::BalanceDivider; auto promPtr1 = std::make_shared>>(); auto fut1 = promPtr1->get_future(); diff --git a/UnitTests/TestWebSockets.cpp b/UnitTests/TestWebSockets.cpp index f544b08a6..99647dfd4 100644 --- a/UnitTests/TestWebSockets.cpp +++ b/UnitTests/TestWebSockets.cpp @@ -1,7 +1,7 @@ /* *********************************************************************************** -* Copyright (C) 2020 - 2020, BlockSettle AB +* Copyright (C) 2020 - 2021, BlockSettle AB * Distributed under the GNU Affero General Public License (AGPL v3) * See LICENSE or http://www.gnu.org/licenses/agpl.html * @@ -99,7 +99,7 @@ namespace { std::shared_ptr logger_; std::promise connected_; std::promise disconnected_; - ArmoryThreading::TimedQueue> data_; + Armory::Threading::TimedQueue> data_; }; class TestClientConnListener : public DataConnectionListener @@ -132,7 +132,7 @@ namespace { std::shared_ptr logger_; std::promise connected_; std::promise disconnected_; - ArmoryThreading::TimedQueue data_; + Armory::Threading::TimedQueue data_; std::promise error_; }; @@ -162,7 +162,7 @@ namespace { const auto kDefaultTimeout = 10000ms; template - T getFeature(ArmoryThreading::TimedQueue &data, std::chrono::milliseconds timeout = kDefaultTimeout) + T getFeature(Armory::Threading::TimedQueue &data, std::chrono::milliseconds timeout = kDefaultTimeout) { return data.pop_front(timeout); } @@ -357,7 +357,7 @@ TEST_F(TestWebSocket, BrokenData) auto packet = CryptoPRNG::generateRandom(rand() % 10000).toBinStr(); ASSERT_TRUE(client_->send(packet)); - ASSERT_THROW(getFeature(serverListener_->data_, 100ms), ArmoryThreading::StackTimedOutException); + ASSERT_THROW(getFeature(serverListener_->data_, 100ms), Armory::Threading::StackTimedOutException); } TEST_F(TestWebSocket, ClientStartsFirst) diff --git a/UnitTests/main.cpp b/UnitTests/main.cpp index 237a150ea..aea4ba7ef 100644 --- a/UnitTests/main.cpp +++ b/UnitTests/main.cpp @@ -26,6 +26,7 @@ #include #include "TestEnv.h" +#include "ArmoryConfig.h" #include "BinaryData.h" #include @@ -109,8 +110,7 @@ int main(int argc, char** argv) qRegisterMetaType(); //::testing::AddGlobalTestEnvironment(new TestEnv(logger)); - - NetworkConfig::selectNetwork(NETWORK_MODE_TESTNET); + Armory::Config::NetworkSettings::selectNetwork(Armory::Config::NETWORK_MODE_TESTNET); QTimer::singleShot(0, [] { rc = RUN_ALL_TESTS(); diff --git a/common b/common index e631d0155..7a79d2caa 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit e631d015520bc9c252e79cc85fd6309a6c759d54 +Subproject commit 7a79d2caa5f8541efeeb11ab3861df1dbe7cf8ba diff --git a/generate.py b/generate.py index aadab7315..b67f28ae8 100644 --- a/generate.py +++ b/generate.py @@ -1,7 +1,7 @@ # # # *********************************************************************************** -# * Copyright (C) 2011 - 2020, BlockSettle AB +# * Copyright (C) 2018 - 2021, BlockSettle AB # * Distributed under the GNU Affero General Public License (AGPL v3) # * See LICENSE or http://www.gnu.org/licenses/agpl.html # * @@ -22,24 +22,25 @@ sys.path.insert(0, os.path.join('common')) sys.path.insert(0, os.path.join('common', 'build_scripts')) -from build_scripts.settings import Settings -from build_scripts.protobuf_settings import ProtobufSettings -from build_scripts.gtest_settings import GtestSettings -from build_scripts.jom_settings import JomSettings -from build_scripts.qt_settings import QtSettings -from build_scripts.spdlog_settings import SpdlogSettings -from build_scripts.zeromq_settings import ZeroMQSettings -from build_scripts.libqrencode_settings import LibQREncode -from build_scripts.mpir_settings import MPIRSettings -from build_scripts.libbtc_settings import LibBTC -from build_scripts.openssl_settings import OpenSslSettings -from build_scripts.websockets_settings import WebsocketsSettings +from build_scripts.bip_protocols_settings import BipProtocolsSettings +from build_scripts.botan_settings import BotanSettings +from build_scripts.gtest_settings import GtestSettings +from build_scripts.hidapi_settings import HidapiSettings +from build_scripts.jom_settings import JomSettings +from build_scripts.libbtc_settings import LibBTC from build_scripts.libchacha20poly1305_settings import LibChaCha20Poly1305Settings -from build_scripts.botan_settings import BotanSettings -from build_scripts.hidapi_settings import HidapiSettings -from build_scripts.libusb_settings import LibusbSettings -from build_scripts.trezor_common_settings import TrezorCommonSettings -from build_scripts.bip_protocols_settings import BipProtocolsSettings +from build_scripts.libqrencode_settings import LibQREncode +from build_scripts.libusb_settings import LibusbSettings +from build_scripts.mpir_settings import MPIRSettings +from build_scripts.nlohmann_json_settings import NLohmanJson +from build_scripts.openssl_settings import OpenSslSettings +from build_scripts.protobuf_settings import ProtobufSettings +from build_scripts.qt_settings import QtSettings +from build_scripts.settings import Settings +from build_scripts.spdlog_settings import SpdlogSettings +from build_scripts.trezor_common_settings import TrezorCommonSettings +from build_scripts.websockets_settings import WebsocketsSettings +from build_scripts.zeromq_settings import ZeroMQSettings def generate_project(build_mode, link_mode, build_production, hide_warnings, cmake_flags, build_tests, build_tracker): project_settings = Settings(build_mode, link_mode) @@ -70,7 +71,8 @@ def generate_project(build_mode, link_mode, build_production, hide_warnings, cma HidapiSettings(project_settings), LibusbSettings(project_settings), TrezorCommonSettings(project_settings), - BipProtocolsSettings(project_settings) + BipProtocolsSettings(project_settings), + NLohmanJson(project_settings) ] if build_tests: diff --git a/prepeare_release.py b/prepeare_release.py index 839541961..468747620 100644 --- a/prepeare_release.py +++ b/prepeare_release.py @@ -1,7 +1,7 @@ # # # *********************************************************************************** -# * Copyright (C) 2011 - 2020, BlockSettle AB +# * Copyright (C) 2019 - 2021, BlockSettle AB # * Distributed under the GNU Affero General Public License (AGPL v3) # * See LICENSE or http://www.gnu.org/licenses/agpl.html # *