chore(CPP): integrate tokens balance status-go API (POC)
Exposes status-go API for retrieving networks and tokens Tests for the exposed API Introduced boost for the multiprecision library and 256 bits support for balance. Update build instructions Updates: #6321
This commit is contained in:
parent
9f6feb67dd
commit
0ba35d3812
|
@ -9,13 +9,14 @@ project(status-desktop
|
||||||
VERSION ${STATUS_VERSION}
|
VERSION ${STATUS_VERSION}
|
||||||
LANGUAGES CXX)
|
LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
set(PROJECT_ORGANIZATION_DOMAIN "status.im")
|
set(PROJECT_ORGANIZATION_DOMAIN "status.im")
|
||||||
set(PROJECT_ORGANIZATION_NAME "Status")
|
set(PROJECT_ORGANIZATION_NAME "Status")
|
||||||
set(PROJECT_APPLICATION_NAME "Status Desktop")
|
set(PROJECT_APPLICATION_NAME "Status Desktop")
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
## Setup dependencies
|
## Setup dependencies
|
||||||
|
|
||||||
|
Note: if not stated otherwise the commands should be run from the root of the source tree
|
||||||
|
|
||||||
### 1. conancenter
|
### 1. conancenter
|
||||||
|
|
||||||
Execute `conan remote list`. It should return this line among the results:
|
Execute `conan remote list`. It should return this line among the results:
|
||||||
|
@ -23,28 +25,29 @@ conan profile update settings.compiler.libcxx=libstdc++11 default
|
||||||
|
|
||||||
### 2. Install dependencies
|
### 2. Install dependencies
|
||||||
|
|
||||||
|
Install latest Qt release 6.3.X
|
||||||
|
|
||||||
Platform specific conan profile
|
Platform specific conan profile
|
||||||
|
|
||||||
- Macos:
|
- Macos:
|
||||||
- Intel: `conan install . --profile=vendor/conan-configs/apple-arm64.ini -s build_type=Release --build=missing -if=build/conan`
|
- Apple silicon: `conan install . --profile=vendor/conan-configs/apple-arm64.ini -s build_type=Release --build=missing -if=build/conan -of=build`
|
||||||
- Apple silicon: `conan install . --profile=vendor/conan-configs/apple-x86_64.ini -s build_type=Release --build=missing -if=build/conan`
|
- Intel: `conan install . --profile=vendor/conan-configs/apple-x86_64.ini -s build_type=Release --build=missing -if=build/conan -of=build`
|
||||||
- Windows: TODO
|
- Windows: TODO
|
||||||
- Linux: `conan install . --profile=./vendor/conan-configs/linux.ini -s build_type=Release --build=missing -if=build/conan`
|
- Linux: `conan install . --profile=./vendor/conan-configs/linux.ini -s build_type=Release --build=missing -if=build/conan -of=build`
|
||||||
|
|
||||||
|
|
||||||
## Buid, test & run
|
## Buid, test & run
|
||||||
|
|
||||||
Update `CMake` to the [Latest Release](https://cmake.org/download/)
|
Update `CMake` to the [Latest Release](https://cmake.org/download/) or use the Qt's "$QTBASE/Tools/CMake/..."
|
||||||
|
|
||||||
### Build with conan
|
### Build with conan
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# linux
|
# linux
|
||||||
CMAKE_PREFIX_PATH="$HOME/Qt/6.4.0/gcc_64" conan build . -if=build/conan -bf=build
|
CMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/gcc_64" conan build . -if=build/conan -bf=build
|
||||||
|
|
||||||
# MacOS: CMAKE_PREFIX_PATH="$HOME/Qt/6.4.0/macos" conan build . -if=build/conan -bf=build
|
# MacOS: CMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/macos" conan build . -if=build/conan -bf=build
|
||||||
|
|
||||||
# Windows: CMAKE_PREFIX_PATH="$HOME/Qt/6.4.0/mingw_64" conan build . -if=build/conan -bf=build
|
# Windows: CMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/mingw_64" conan build . -if=build/conan -bf=build
|
||||||
|
|
||||||
ctest -VV -C Release
|
ctest -VV -C Release
|
||||||
./status-desktop
|
./status-desktop
|
||||||
|
@ -54,11 +57,17 @@ ctest -VV -C Release
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# linux
|
# linux
|
||||||
cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.4.0/gcc_64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
|
cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/gcc_64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
|
||||||
|
|
||||||
# MacOS: cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.4.0/macos" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
|
# MacOS: cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/macos" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
|
||||||
|
|
||||||
# Windows: cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.4.0/mingw_64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
|
# Windows: cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/mingw_64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake
|
||||||
|
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Run tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ctest --test-dir ./build
|
||||||
|
```
|
||||||
|
|
17
conanfile.py
17
conanfile.py
|
@ -1,17 +0,0 @@
|
||||||
from conans import ConanFile, CMake
|
|
||||||
|
|
||||||
|
|
||||||
class StatusDesktop(ConanFile):
|
|
||||||
name = "status-desktop"
|
|
||||||
settings = "os", "compiler", "build_type", "arch"
|
|
||||||
|
|
||||||
requires = "gtest/1.11.0", "nlohmann_json/3.10.5" # "fruit/3.6.0",
|
|
||||||
|
|
||||||
# cmake_find_package and cmake_find_package_multi should be substituted with CMakeDeps
|
|
||||||
# as soon as Conan 2.0 is released and all conan-center packages are adapted
|
|
||||||
generators = "CMakeToolchain", "cmake_find_package", "cmake_find_package_multi"
|
|
||||||
|
|
||||||
def build(self):
|
|
||||||
cmake = CMake(self)
|
|
||||||
cmake.configure()
|
|
||||||
cmake.build()
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[requires]
|
||||||
|
gtest/1.11.0
|
||||||
|
nlohmann_json/3.10.5
|
||||||
|
boost/1.79.0
|
||||||
|
|
||||||
|
#"fruit/3.6.0"
|
||||||
|
|
||||||
|
[generators]
|
||||||
|
CMakeToolchain
|
||||||
|
CMakeDeps
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <compare>
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
@ -19,25 +20,38 @@ class NamedType
|
||||||
public:
|
public:
|
||||||
using UnderlyingType = T;
|
using UnderlyingType = T;
|
||||||
|
|
||||||
// constructor
|
|
||||||
explicit constexpr NamedType(T const& value) : m_value(value) {}
|
explicit constexpr NamedType(T const& value) : m_value(value) {}
|
||||||
template<typename T_ = T, typename = IsNotReference<T_>>
|
template<typename T_ = T, typename = IsNotReference<T_>>
|
||||||
explicit constexpr NamedType(T&& value) : m_value(std::move(value)) {}
|
explicit constexpr NamedType(T&& value) : m_value(std::move(value)) {}
|
||||||
explicit constexpr NamedType() = default;
|
explicit constexpr NamedType() = default;
|
||||||
|
|
||||||
// get
|
constexpr T& get() {
|
||||||
constexpr T& get() { return m_value; }
|
return m_value;
|
||||||
constexpr std::remove_reference_t<T> const& get() const {return m_value; }
|
}
|
||||||
|
|
||||||
bool operator<(const NamedType<T, Parameter> &) const = default;
|
constexpr std::remove_reference_t<T> const& get() const {
|
||||||
bool operator>(const NamedType<T, Parameter> &) const = default;
|
return m_value;
|
||||||
bool operator<=(const NamedType<T, Parameter> &) const = default;
|
}
|
||||||
bool operator>=(const NamedType<T, Parameter> &) const = default;
|
|
||||||
bool operator==(const NamedType<T, Parameter> &) const = default;
|
#if defined __cpp_impl_three_way_comparison && defined __cpp_lib_three_way_comparison
|
||||||
bool operator!=(const NamedType<T, Parameter> &) const = default;
|
friend auto operator<=>(const NamedType<T, Parameter>& l, const NamedType<T, Parameter>& r) noexcept {
|
||||||
|
return l.m_value <=> r.m_value;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
bool operator<(const NamedType<T, Parameter> &other) const { return m_value < other.m_value; };
|
||||||
|
bool operator>(const NamedType<T, Parameter> &other) const { return m_value > other.m_value; };
|
||||||
|
bool operator<=(const NamedType<T, Parameter> &other) const { return m_value <= other.m_value; };
|
||||||
|
bool operator>=(const NamedType<T, Parameter> &other) const { return m_value >= other.m_value; };
|
||||||
|
bool operator==(const NamedType<T, Parameter> &other) const { return m_value == other.m_value; };
|
||||||
|
bool operator!=(const NamedType<T, Parameter> &other) const { return m_value != other.m_value; };
|
||||||
|
#endif
|
||||||
|
|
||||||
|
T &operator=(NamedType<T, Parameter> const& rhs) {
|
||||||
|
return m_value = rhs.m_value;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T m_value;
|
T m_value{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, typename P>
|
template <typename T, typename P>
|
||||||
|
@ -59,7 +73,6 @@ template <typename T, typename Parameter>
|
||||||
struct hash<Status::Helpers::NamedType<T, Parameter>>
|
struct hash<Status::Helpers::NamedType<T, Parameter>>
|
||||||
{
|
{
|
||||||
using NamedType = Status::Helpers::NamedType<T, Parameter>;
|
using NamedType = Status::Helpers::NamedType<T, Parameter>;
|
||||||
using checkIfHashable = typename std::enable_if<NamedType::is_hashable, void>::type;
|
|
||||||
|
|
||||||
size_t operator()(NamedType const& x) const
|
size_t operator()(NamedType const& x) const
|
||||||
{
|
{
|
||||||
|
@ -67,4 +80,4 @@ struct hash<Status::Helpers::NamedType<T, Parameter>>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace std
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "helpers.h"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
@ -31,6 +34,23 @@ struct adl_serializer<QString> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct adl_serializer<QByteArray> {
|
||||||
|
static void to_json(json& j, const QByteArray& data) {
|
||||||
|
j = data.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void from_json(const json& j, QByteArray& data) {
|
||||||
|
auto str = j.get<std::string>();
|
||||||
|
if(str.size() >= 2 && Status::Helpers::iequals(str, "0x"s, 2))
|
||||||
|
data = QByteArray::fromHex(QByteArray::fromRawData(str.c_str() + 2 * sizeof(str[0]), str.size() - 2));
|
||||||
|
else
|
||||||
|
data = QByteArray::fromStdString(str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct adl_serializer<QColor> {
|
struct adl_serializer<QColor> {
|
||||||
static void to_json(json& j, const QColor& color) {
|
static void to_json(json& j, const QColor& color) {
|
||||||
|
@ -38,7 +58,18 @@ struct adl_serializer<QColor> {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void from_json(const json& j, QColor& color) {
|
static void from_json(const json& j, QColor& color) {
|
||||||
color = QColor(Status::toQString(j.get<std::string>()));
|
color = QColor(QString::fromStdString(j.get<std::string>()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct adl_serializer<QUrl> {
|
||||||
|
static void to_json(json& j, const QUrl& url) {
|
||||||
|
j = url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void from_json(const json& j, QUrl& url) {
|
||||||
|
url = QUrl(QString::fromStdString(j.get<std::string>()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "Helpers/BuildConfiguration.h"
|
#include "Helpers/BuildConfiguration.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace Status::Helpers {
|
namespace Status::Helpers {
|
||||||
|
|
||||||
constexpr bool isDebugBuild()
|
constexpr bool isDebugBuild()
|
||||||
|
@ -11,4 +15,18 @@ constexpr bool isDebugBuild()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Case insensitive comparision with optional limitation to first \c len characters
|
||||||
|
/// \note \c T entry type must support \c tolower
|
||||||
|
/// \todo test me
|
||||||
|
template<typename T>
|
||||||
|
bool iequals(const T& a, const T& b, size_t len = -1)
|
||||||
|
{
|
||||||
|
return len < a.size() && len < b.size() &&
|
||||||
|
std::equal(a.begin(), len >= 0 ? a.end() : a.begin() + len,
|
||||||
|
b.begin(), len >= 0 ? b.end() : b.begin() + len,
|
||||||
|
[](auto a, auto b) {
|
||||||
|
return tolower(a) == tolower(b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,14 +139,10 @@ QString AccountsService::login(MultiAccount account, const QString& password)
|
||||||
|
|
||||||
const auto hashedPassword(Utils::hashPassword(password));
|
const auto hashedPassword(Utils::hashPassword(password));
|
||||||
|
|
||||||
const QString installationId(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
|
||||||
const QJsonObject nodeConfig(getDefaultNodeConfig(installationId));
|
|
||||||
|
|
||||||
const QString thumbnailImage;
|
const QString thumbnailImage;
|
||||||
const QString largeImage;
|
const QString largeImage;
|
||||||
// TODO DEV
|
|
||||||
const auto response = StatusGo::Accounts::login(account.name, account.keyUid, hashedPassword,
|
const auto response = StatusGo::Accounts::login(account.name, account.keyUid, hashedPassword,
|
||||||
thumbnailImage, largeImage/*, nodeConfig*/);
|
thumbnailImage, largeImage);
|
||||||
if(response.containsError())
|
if(response.containsError())
|
||||||
{
|
{
|
||||||
qWarning() << response.error.message;
|
qWarning() << response.error.message;
|
||||||
|
@ -310,13 +306,6 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedMul
|
||||||
const QString& installationId,
|
const QString& installationId,
|
||||||
const QString& displayName) const
|
const QString& displayName) const
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
auto templateDefaultNetworksJson = getDataFromFile(":/Status/StaticConfig/default-networks.json").value();
|
|
||||||
const auto infuraKey = getDataFromFile(":/Status/StaticConfig/infura_key").value();
|
|
||||||
|
|
||||||
QString defaultNetworksContent = templateDefaultNetworksJson.replace("%INFURA_KEY%", infuraKey);
|
|
||||||
QJsonArray defaultNetworksJson = QJsonDocument::fromJson(defaultNetworksContent.toUtf8()).array();
|
|
||||||
|
|
||||||
return QJsonObject{
|
return QJsonObject{
|
||||||
{"key-uid", account.keyUid},
|
{"key-uid", account.keyUid},
|
||||||
{"mnemonic", account.mnemonic},
|
{"mnemonic", account.mnemonic},
|
||||||
|
@ -332,22 +321,13 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedMul
|
||||||
{"log-level", "INFO"},
|
{"log-level", "INFO"},
|
||||||
{"latest-derived-path", 0},
|
{"latest-derived-path", 0},
|
||||||
{"currency", "usd"},
|
{"currency", "usd"},
|
||||||
//{"networks/networks", defaultNetworksJson},
|
|
||||||
{"networks/networks", QJsonArray()},
|
{"networks/networks", QJsonArray()},
|
||||||
//{"networks/current-network", Constants::General::DefaultNetworkName},
|
|
||||||
{"networks/current-network", ""},
|
{"networks/current-network", ""},
|
||||||
{"wallet/visible-tokens", QJsonObject()},
|
{"wallet/visible-tokens", QJsonObject()},
|
||||||
//{"wallet/visible-tokens", {
|
|
||||||
// {Constants::General::DefaultNetworkName, QJsonArray{"SNT"}}
|
|
||||||
// }
|
|
||||||
//},
|
|
||||||
{"waku-enabled", true},
|
{"waku-enabled", true},
|
||||||
{"appearance", 0},
|
{"appearance", 0},
|
||||||
{"installation-id", installationId}
|
{"installation-id", installationId}
|
||||||
};
|
};
|
||||||
} catch (std::bad_optional_access) {
|
|
||||||
return QJsonObject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject AccountsService::getAccountSettings(const QString& accountId, const QString& installationId, const QString &displayName) const
|
QJsonObject AccountsService::getAccountSettings(const QString& accountId, const QString& installationId, const QString &displayName) const
|
||||||
|
@ -388,19 +368,31 @@ QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId)
|
||||||
auto fleetJson = getDataFromFile(":/Status/StaticConfig/fleets.json").value();
|
auto fleetJson = getDataFromFile(":/Status/StaticConfig/fleets.json").value();
|
||||||
auto infuraKey = getDataFromFile(":/Status/StaticConfig/infura_key").value();
|
auto infuraKey = getDataFromFile(":/Status/StaticConfig/infura_key").value();
|
||||||
|
|
||||||
auto nodeConfigJsonStr = templateNodeConfigJsonStr.replace("%INSTALLATIONID%", installationId)
|
auto templateDefaultNetworksJson = getDataFromFile(":/Status/StaticConfig/default-networks.json").value();
|
||||||
.replace("%INFURA_KEY%", infuraKey);
|
QString defaultNetworksContent = templateDefaultNetworksJson.replace("%INFURA_TOKEN_RESOLVED%", infuraKey);
|
||||||
QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigJsonStr.toUtf8()).object();
|
QJsonArray defaultNetworksJson = QJsonDocument::fromJson(defaultNetworksContent.toUtf8()).array();
|
||||||
QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].toObject();
|
|
||||||
|
|
||||||
|
auto DEFAULT_TORRENT_CONFIG_PORT = 9025;
|
||||||
|
auto DEFAULT_TORRENT_CONFIG_DATADIR = m_statusgoDataDir / "archivedata";
|
||||||
|
auto DEFAULT_TORRENT_CONFIG_TORRENTDIR = m_statusgoDataDir / "torrents";
|
||||||
|
|
||||||
|
auto nodeConfigJsonStr = templateNodeConfigJsonStr
|
||||||
|
.replace("%INSTALLATIONID%", installationId)
|
||||||
|
.replace("%INFURA_TOKEN_RESOLVED%", infuraKey)
|
||||||
|
.replace("%DEFAULT_TORRENT_CONFIG_PORT%", QString::number(DEFAULT_TORRENT_CONFIG_PORT))
|
||||||
|
.replace("%DEFAULT_TORRENT_CONFIG_DATADIR%", DEFAULT_TORRENT_CONFIG_DATADIR.c_str())
|
||||||
|
.replace("%DEFAULT_TORRENT_CONFIG_TORRENTDIR%", DEFAULT_TORRENT_CONFIG_TORRENTDIR.c_str());
|
||||||
|
QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigJsonStr.toUtf8()).object();
|
||||||
|
|
||||||
|
QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].toObject();
|
||||||
QJsonObject fleetsJson = QJsonDocument::fromJson(fleetJson.toUtf8()).object()["fleets"].toObject();
|
QJsonObject fleetsJson = QJsonDocument::fromJson(fleetJson.toUtf8()).object()["fleets"].toObject();
|
||||||
auto fleet = fleetsJson[Constants::Fleet::Prod].toObject();
|
auto fleet = fleetsJson[Constants::Fleet::Prod].toObject();
|
||||||
|
|
||||||
clusterConfig["Fleet"] = Constants::Fleet::Prod;
|
clusterConfig["Fleet"] = Constants::Fleet::Prod;
|
||||||
clusterConfig["BootNodes"] = getNodes(fleet, Constants::FleetNodes::Bootnodes);
|
clusterConfig["BootNodes"] = getNodes(fleet, Constants::FleetNodes::Bootnodes);
|
||||||
clusterConfig["TrustedMailServers"] = getNodes(fleet, Constants::FleetNodes::Mailservers);
|
clusterConfig["TrustedMailServers"] = getNodes(fleet, Constants::FleetNodes::Mailservers);
|
||||||
clusterConfig["StaticNodes"] = getNodes(fleet, Constants::FleetNodes::Whisper);
|
clusterConfig["StaticNodes"] = getNodes(fleet, Constants::FleetNodes::Whisper);
|
||||||
clusterConfig["RendezvousNodes"] = getNodes(fleet, Constants::FleetNodes::Rendezvous);
|
clusterConfig["RendezvousNodes"] = getNodes(fleet, Constants::FleetNodes::Rendezvous);
|
||||||
|
clusterConfig["DiscV5BootstrapNodes"] = QJsonArray();
|
||||||
clusterConfig["RelayNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
clusterConfig["RelayNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
clusterConfig["StoreNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
clusterConfig["StoreNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
clusterConfig["FilterNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
clusterConfig["FilterNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
|
@ -408,6 +400,17 @@ QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId)
|
||||||
|
|
||||||
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
||||||
|
|
||||||
|
nodeConfigJson["NetworkId"] = defaultNetworksJson[0].toObject()["chainId"];
|
||||||
|
nodeConfigJson["DataDir"] = "ethereum";
|
||||||
|
auto upstreamConfig = nodeConfigJson["UpstreamConfig"].toObject();
|
||||||
|
upstreamConfig["Enabled"] = true;
|
||||||
|
upstreamConfig["URL"] = defaultNetworksJson[0].toObject()["rpcUrl"];
|
||||||
|
nodeConfigJson["UpstreamConfig"] = upstreamConfig;
|
||||||
|
auto shhextConfig = nodeConfigJson["ShhextConfig"].toObject();
|
||||||
|
shhextConfig["InstallationID"] = installationId;
|
||||||
|
nodeConfigJson["ShhextConfig"] = shhextConfig;
|
||||||
|
nodeConfigJson["Networks"] = defaultNetworksJson;
|
||||||
|
|
||||||
nodeConfigJson["KeyStoreDir"] = toQString(fs::relative(m_keyStoreDir, m_statusgoDataDir));
|
nodeConfigJson["KeyStoreDir"] = toQString(fs::relative(m_keyStoreDir, m_statusgoDataDir));
|
||||||
return nodeConfigJson;
|
return nodeConfigJson;
|
||||||
} catch (std::bad_optional_access) {
|
} catch (std::bad_optional_access) {
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
/// \see ServiceInterface
|
/// \see ServiceInterface
|
||||||
bool setKeyStoreDir(const QString &key) override;
|
bool setKeyStoreDir(const QString &key) override;
|
||||||
|
|
||||||
|
/// \todo login@src/app_service/service/accounts/service.nim adds custom configuration for defaults
|
||||||
|
/// to account for legacy user DBs. See if this is required to replicate or add proper migration logic
|
||||||
QString login(MultiAccount account, const QString& password) override;
|
QString login(MultiAccount account, const QString& password) override;
|
||||||
|
|
||||||
void clear() override;
|
void clear() override;
|
||||||
|
|
|
@ -12,9 +12,6 @@ add_executable(TestOnboardingQml
|
||||||
"main.cpp"
|
"main.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
# no need to copy around qml test files for shadow builds - just set the respective define
|
# no need to copy around qml test files for shadow builds - just set the respective define
|
||||||
add_compile_definitions(QUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
add_compile_definitions(QUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ project(StatusGoQt
|
||||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||||
|
|
||||||
find_package(nlohmann_json 3.10.5 REQUIRED)
|
find_package(nlohmann_json 3.10.5 REQUIRED)
|
||||||
|
find_package(Boost)
|
||||||
|
|
||||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core Concurrent Gui REQUIRED)
|
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core Concurrent Gui REQUIRED)
|
||||||
qt6_standard_project_setup()
|
qt6_standard_project_setup()
|
||||||
|
@ -21,6 +22,7 @@ set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
|
||||||
target_link_libraries(${PROJECT_NAME}
|
target_link_libraries(${PROJECT_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
Status::Helpers
|
Status::Helpers
|
||||||
|
Boost::headers
|
||||||
|
|
||||||
PRIVATE
|
PRIVATE
|
||||||
Qt6::Gui
|
Qt6::Gui
|
||||||
|
@ -90,7 +92,12 @@ target_sources(${PROJECT_NAME}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/SignalsManager.cpp
|
||||||
|
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/BigInt.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/BigInt.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/DerivedAddress.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/DerivedAddress.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/NetworkConfiguration.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/Token.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/wallet_types.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.h
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/src/StatusGo/Wallet/WalletApi.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,8 +22,9 @@ const char* statusGoCallPrivateRPC(const char* inputJSON) {
|
||||||
|
|
||||||
HashedPassword hashPassword(const QString &str)
|
HashedPassword hashPassword(const QString &str)
|
||||||
{
|
{
|
||||||
|
// TODO: is utf8 the standard used by NIM also? Will it unlock DBs encrypted with NIM password hashing?
|
||||||
return HashedPassword("0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(),
|
return HashedPassword("0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(),
|
||||||
QCryptographicHash::Keccak_256).toHex()));
|
QCryptographicHash::Keccak_256).toHex().toUpper()));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<RpcError> getRPCErrorInJson(const QJsonObject& json)
|
std::optional<RpcError> getRPCErrorInJson(const QJsonObject& json)
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include "BigInt.h"
|
||||||
|
|
||||||
|
#include <locale>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <Helpers/helpers.h>
|
||||||
|
|
||||||
|
namespace Status {
|
||||||
|
namespace StatusGo::Wallet {
|
||||||
|
|
||||||
|
std::string toHexData(const BigInt &num, bool uppercase)
|
||||||
|
{
|
||||||
|
return num.str(0, std::ios_base::showbase | std::ios_base::hex | (uppercase ? std::ios_base::uppercase : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using namespace QtLiterals;
|
||||||
|
|
||||||
|
bool has0xPrefix(const QByteArray &in) {
|
||||||
|
return in.size() >= 2 && Helpers::iequals(in.first(2), "0x"_qba);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInt parseHex(const std::string &value)
|
||||||
|
{
|
||||||
|
auto data = QByteArray::fromRawData(value.c_str(), value.size());
|
||||||
|
if (!has0xPrefix(data))
|
||||||
|
throw std::runtime_error("BigInt::parseHex missing 0x prefix");
|
||||||
|
if (data.size() == 2)
|
||||||
|
throw std::runtime_error("BigInt::parseHex empty number");
|
||||||
|
if (data.size() > 3 && data[2] == '0')
|
||||||
|
throw std::runtime_error("BigInt::parseHex leading zero");
|
||||||
|
if (data.size() > 66)
|
||||||
|
throw std::runtime_error("BigInt::parseHex more than 256 bits");
|
||||||
|
return BigInt{data.data()};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace StatusGo::Wallet
|
||||||
|
|
||||||
|
QString toQString(const StatusGo::Wallet::BigInt &num)
|
||||||
|
{
|
||||||
|
return QString::fromStdString(num.str(0, std::ios_base::dec));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Status
|
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Helpers/conversions.h>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <boost/multiprecision/cpp_int.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace Status {
|
||||||
|
namespace StatusGo::Wallet {
|
||||||
|
|
||||||
|
using BigInt = boost::multiprecision::uint256_t;
|
||||||
|
|
||||||
|
/// Converts into the 0x prefixed hexadecimal display required by status-go (also uppercase)
|
||||||
|
std::string toHexData(const BigInt &num, bool uppercase = false);
|
||||||
|
|
||||||
|
/// \throws std::runtime_error if value is not a valid status-go hex string
|
||||||
|
/// or value is higher than 2^64 (numbers larger than 256 bits are not accepted)
|
||||||
|
/// \see toHexData
|
||||||
|
BigInt parseHex(const std::string &value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Human readable form
|
||||||
|
QString toQString(const StatusGo::Wallet::BigInt &num);
|
||||||
|
|
||||||
|
} // Status::StatusGo::Wallet
|
||||||
|
|
||||||
|
namespace nlohmann {
|
||||||
|
|
||||||
|
namespace GoWallet = Status::StatusGo::Wallet;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct adl_serializer<GoWallet::BigInt> {
|
||||||
|
static void to_json(json& j, const GoWallet::BigInt& num) {
|
||||||
|
j = GoWallet::toHexData(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void from_json(const json& j, GoWallet::BigInt& num) {
|
||||||
|
num = GoWallet::BigInt(j.get<std::string>());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nlohmann
|
|
@ -16,11 +16,9 @@ using json = nlohmann::json;
|
||||||
|
|
||||||
namespace Status::StatusGo::Wallet {
|
namespace Status::StatusGo::Wallet {
|
||||||
|
|
||||||
/*!
|
/// \brief Define a derived address as returned by the corresponding API
|
||||||
* \brief Define a derived address as returned by the corresponding API
|
/// \note equivalent of status-go's DerivedAddress@api.go
|
||||||
* \note equivalent of status-go's DerivedAddress@api.go
|
/// \see \c getDerivedAddressesForPath
|
||||||
* \see \c getDerivedAddressesForPath
|
|
||||||
*/
|
|
||||||
struct DerivedAddress
|
struct DerivedAddress
|
||||||
{
|
{
|
||||||
Accounts::EOAddress address;
|
Accounts::EOAddress address;
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wallet_types.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
/// \note not sure if this is the best namespace, ok for now
|
||||||
|
namespace Status::StatusGo::Wallet {
|
||||||
|
|
||||||
|
/// \note equivalent of status-go's Network@config.go (params package)
|
||||||
|
struct NetworkConfiguration
|
||||||
|
{
|
||||||
|
ChainID chainId;
|
||||||
|
QString chainName;
|
||||||
|
QUrl rpcUrl;
|
||||||
|
std::optional<QUrl> blockExplorerUrl;
|
||||||
|
std::optional<QUrl> iconUrl;
|
||||||
|
std::optional<QString> nativeCurrencyName;
|
||||||
|
std::optional<QString> nativeCurrencySymbol;
|
||||||
|
unsigned int nativeCurrencyDecimals{};
|
||||||
|
bool isTest{};
|
||||||
|
unsigned int layer{};
|
||||||
|
bool enabled{};
|
||||||
|
QColor chainColor;
|
||||||
|
QString shortName;
|
||||||
|
};
|
||||||
|
|
||||||
|
using NetworkConfigurations = std::vector<NetworkConfiguration>;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NetworkConfiguration, chainId, chainName, rpcUrl, blockExplorerUrl,
|
||||||
|
iconUrl, nativeCurrencyName, nativeCurrencySymbol,
|
||||||
|
nativeCurrencyDecimals, isTest, layer, enabled, chainColor,
|
||||||
|
shortName);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wallet_types.h"
|
||||||
|
|
||||||
|
#include "Accounts/accounts_types.h"
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Accounts = Status::StatusGo::Accounts;
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace Status::StatusGo::Wallet {
|
||||||
|
|
||||||
|
/// \note equivalent of status-go's Token@token.go
|
||||||
|
/// \see \c getDerivedAddressesForPath
|
||||||
|
struct Token
|
||||||
|
{
|
||||||
|
Accounts::EOAddress address;
|
||||||
|
QString name;
|
||||||
|
QString symbol;
|
||||||
|
QColor color;
|
||||||
|
unsigned int decimals;
|
||||||
|
ChainID chainId;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Tokens = std::vector<Token>;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Token, address, name,symbol,
|
||||||
|
color, decimals, chainId);
|
||||||
|
|
||||||
|
}
|
|
@ -34,4 +34,67 @@ DerivedAddresses getDerivedAddressesForPath(const HashedPassword &password, cons
|
||||||
return resultJson.get<CallPrivateRpcResponse>().result;
|
return resultJson.get<CallPrivateRpcResponse>().result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkConfigurations getEthereumChains(bool onlyEnabled)
|
||||||
|
{
|
||||||
|
std::vector<json> params = {onlyEnabled};
|
||||||
|
json inputJson = {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"method", "wallet_getEthereumChains"},
|
||||||
|
{"params", params}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
|
||||||
|
const auto &data = resultJson.get<CallPrivateRpcResponse>().result;
|
||||||
|
return data.is_null() ? nlohmann::json() : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tokens getTokens(const ChainID &chainId)
|
||||||
|
{
|
||||||
|
std::vector<json> params = {chainId};
|
||||||
|
json inputJson = {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"method", "wallet_getTokens"},
|
||||||
|
{"params", params}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
|
||||||
|
const auto &data = resultJson.get<CallPrivateRpcResponse>().result;
|
||||||
|
return data.is_null() ? nlohmann::json() : data;
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenBalances getTokensBalancesForChainIDs(const std::vector<ChainID> &chainIds,
|
||||||
|
const std::vector<Accounts::EOAddress> accounts,
|
||||||
|
const std::vector<Accounts::EOAddress> tokens)
|
||||||
|
{
|
||||||
|
std::vector<json> params = {chainIds, accounts, tokens};
|
||||||
|
json inputJson = {
|
||||||
|
{"jsonrpc", "2.0"},
|
||||||
|
{"method", "wallet_getTokensBalancesForChainIDs"},
|
||||||
|
{"params", params}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
|
||||||
|
TokenBalances resultData;
|
||||||
|
const auto &data = resultJson.get<CallPrivateRpcResponse>().result;
|
||||||
|
// Workaround to exception "type must be array, but is object" for custom key-types
|
||||||
|
// TODO: find out why
|
||||||
|
std::map<std::string, std::map<std::string, BigInt>> dataMap = data.is_null() ? nlohmann::json() : data;
|
||||||
|
for(const auto &keyIt : dataMap) {
|
||||||
|
std::map<Accounts::EOAddress, BigInt> val;
|
||||||
|
for(const auto &valIt : keyIt.second)
|
||||||
|
val.emplace(QString::fromStdString(valIt.first), valIt.second);
|
||||||
|
resultData.emplace(QString::fromStdString(keyIt.first), std::move(val));
|
||||||
|
}
|
||||||
|
return resultData;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespaces
|
} // namespaces
|
||||||
|
|
|
@ -2,7 +2,12 @@
|
||||||
|
|
||||||
#include "Accounts/ChatOrWalletAccount.h"
|
#include "Accounts/ChatOrWalletAccount.h"
|
||||||
#include "Accounts/accounts_types.h"
|
#include "Accounts/accounts_types.h"
|
||||||
|
#include "BigInt.h"
|
||||||
|
|
||||||
#include "DerivedAddress.h"
|
#include "DerivedAddress.h"
|
||||||
|
#include "NetworkConfiguration.h"
|
||||||
|
#include "Token.h"
|
||||||
|
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -16,4 +21,25 @@ namespace Status::StatusGo::Wallet
|
||||||
/// \throws \c CallPrivateRpcError
|
/// \throws \c CallPrivateRpcError
|
||||||
DerivedAddresses getDerivedAddressesForPath(const HashedPassword &password, const Accounts::EOAddress &derivedFrom, const Accounts::DerivationPath &path, int pageSize, int pageNumber);
|
DerivedAddresses getDerivedAddressesForPath(const HashedPassword &password, const Accounts::EOAddress &derivedFrom, const Accounts::DerivationPath &path, int pageSize, int pageNumber);
|
||||||
|
|
||||||
|
/// \note status-go's GetEthereumChains@api.go which calls
|
||||||
|
/// NetworkManager@client.go -> network.Manager.get()
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
NetworkConfigurations getEthereumChains(bool onlyEnabled);
|
||||||
|
|
||||||
|
/// \note status-go's GetEthereumChains@api.go which calls
|
||||||
|
/// NetworkManager@client.go -> network.Manager.get()
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
NetworkConfigurations getEthereumChains(bool onlyEnabled);
|
||||||
|
|
||||||
|
|
||||||
|
/// \note status-go's GetTokens@api.go -> TokenManager.getTokens@token.go
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
Tokens getTokens(const ChainID &chainId);
|
||||||
|
|
||||||
|
using TokenBalances = std::map<Accounts::EOAddress, std::map<Accounts::EOAddress, BigInt>>;
|
||||||
|
/// \note status-go's @api.go -> <xx>@<xx>.go
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
TokenBalances getTokensBalancesForChainIDs(const std::vector<ChainID> &chainIds,
|
||||||
|
const std::vector<Accounts::EOAddress> accounts,
|
||||||
|
const std::vector<Accounts::EOAddress> tokens);
|
||||||
} // namespaces
|
} // namespaces
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
#include <Helpers/NamedType.h>
|
||||||
|
#include <Helpers/conversions.h>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
/// Defines phantom types for strong typing
|
||||||
|
namespace Status::StatusGo::Wallet {
|
||||||
|
|
||||||
|
using ChainID = Helpers::NamedType<unsigned long long int, struct ChainIDTag>;
|
||||||
|
|
||||||
|
}
|
|
@ -12,9 +12,6 @@ add_executable(TestStatusQ
|
||||||
"main.cpp"
|
"main.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
# no need to copy around qml test files for shadow builds - just set the respective define
|
# no need to copy around qml test files for shadow builds - just set the respective define
|
||||||
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ public:
|
||||||
|
|
||||||
/// \note On account creation \c accounts are updated with the newly created wallet account
|
/// \note On account creation \c accounts are updated with the newly created wallet account
|
||||||
NewWalletAccountController(std::shared_ptr<AccountsModel> accounts);
|
NewWalletAccountController(std::shared_ptr<AccountsModel> accounts);
|
||||||
~NewWalletAccountController();
|
|
||||||
|
|
||||||
/// Called by QML engine to register the instance. QML takes ownership of the instance
|
/// Called by QML engine to register the instance. QML takes ownership of the instance
|
||||||
static NewWalletAccountController *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
static NewWalletAccountController *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
||||||
|
|
|
@ -9,6 +9,6 @@ Item {
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "$$$$$"
|
text: asset.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,9 +31,6 @@ NewWalletAccountController::NewWalletAccountController(std::shared_ptr<Helpers::
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
NewWalletAccountController::~NewWalletAccountController()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QAbstractListModel* NewWalletAccountController::mainAccountsModel()
|
QAbstractListModel* NewWalletAccountController::mainAccountsModel()
|
||||||
{
|
{
|
||||||
|
@ -58,10 +55,10 @@ void NewWalletAccountController::setDerivationPath(const QString &newDerivationP
|
||||||
emit derivationPathChanged();
|
emit derivationPathChanged();
|
||||||
|
|
||||||
auto oldCustom = m_customDerivationPath;
|
auto oldCustom = m_customDerivationPath;
|
||||||
auto found = searchDerivationPath(m_derivationPath);
|
const auto &[derivedPath, index]= searchDerivationPath(m_derivationPath);
|
||||||
m_customDerivationPath = std::get<0>(found) == nullptr;
|
m_customDerivationPath = derivedPath == nullptr;
|
||||||
if(!m_customDerivationPath && !std::get<0>(found).get()->alreadyCreated())
|
if(!m_customDerivationPath && !derivedPath.get()->alreadyCreated())
|
||||||
updateSelectedDerivedAddress(std::get<1>(found), std::get<0>(found));
|
updateSelectedDerivedAddress(index, derivedPath);
|
||||||
|
|
||||||
if(m_customDerivationPath != oldCustom)
|
if(m_customDerivationPath != oldCustom)
|
||||||
emit customDerivationPathChanged();
|
emit customDerivationPathChanged();
|
||||||
|
|
|
@ -1,76 +1,122 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "testnet_rpc",
|
"chainId": 1,
|
||||||
"etherscan-link": "https://ropsten.etherscan.io/address/",
|
"chainName": "Mainnet",
|
||||||
"name": "Ropsten with upstream RPC",
|
"rpcUrl": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"config": {
|
"blockExplorerUrl": "https://etherscan.io/",
|
||||||
"NetworkId": 3,
|
"iconUrl": "network/Network=Ethereum",
|
||||||
"DataDir": "/ethereum/testnet_rpc",
|
"chainColor": "#627EEA",
|
||||||
"UpstreamConfig": {
|
"shortName": "eth",
|
||||||
"Enabled": true,
|
"nativeCurrencyName": "Ether",
|
||||||
"URL": "https://ropsten.infura.io/v3/%INFURA_KEY%"
|
"nativeCurrencySymbol": "ETH",
|
||||||
}
|
"nativeCurrencyDecimals": 18,
|
||||||
}
|
"isTest": false,
|
||||||
},
|
"layer": 1,
|
||||||
{
|
"enabled": true
|
||||||
"id": "rinkeby_rpc",
|
},
|
||||||
"etherscan-link": "https://rinkeby.etherscan.io/address/",
|
{
|
||||||
"name": "Rinkeby with upstream RPC",
|
"chainId": 3,
|
||||||
"config": {
|
"chainName": "Ropsten",
|
||||||
"NetworkId": 4,
|
"rpcUrl": "https://ropsten.infura.io/v3%INFURA_TOKEN_RESOLVED%",
|
||||||
"DataDir": "/ethereum/rinkeby_rpc",
|
"blockExplorerUrl": "https://ropsten.etherscan.io/",
|
||||||
"UpstreamConfig": {
|
"iconUrl": "network/Network=Tetnet",
|
||||||
"Enabled": true,
|
"chainColor": "#939BA1",
|
||||||
"URL": "https://rinkeby.infura.io/v3/%INFURA_KEY%"
|
"shortName": "ropEth",
|
||||||
}
|
"nativeCurrencyName": "Ether",
|
||||||
}
|
"nativeCurrencySymbol": "ETH",
|
||||||
},
|
"nativeCurrencyDecimals": 18,
|
||||||
{
|
"isTest": true,
|
||||||
"id": "goerli_rpc",
|
"layer": 1,
|
||||||
"etherscan-link": "https://goerli.etherscan.io/address/",
|
"enabled": true
|
||||||
"name": "Goerli with upstream RPC",
|
},
|
||||||
"config": {
|
{
|
||||||
"NetworkId": 5,
|
"chainId": 4,
|
||||||
"DataDir": "/ethereum/goerli_rpc",
|
"chainName": "Rinkeby",
|
||||||
"UpstreamConfig": {
|
"rpcUrl": "https://rinkeby.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"Enabled": true,
|
"blockExplorerUrl": "https://rinkeby.etherscan.io/",
|
||||||
"URL": "https://goerli.blockscout.com/"
|
"iconUrl": "network/Network=Tetnet",
|
||||||
}
|
"chainColor": "#939BA1",
|
||||||
}
|
"shortName": "rinEth",
|
||||||
},
|
"nativeCurrencyName": "Ether",
|
||||||
{
|
"nativeCurrencySymbol": "ETH",
|
||||||
"id": "mainnet_rpc",
|
"nativeCurrencyDecimals": 18,
|
||||||
"etherscan-link": "https://etherscan.io/address/",
|
"isTest": true,
|
||||||
"name": "Mainnet with upstream RPC",
|
"layer": 1,
|
||||||
"config": {
|
"enabled": false
|
||||||
"NetworkId": 1,
|
},
|
||||||
"DataDir": "/ethereum/mainnet_rpc",
|
{
|
||||||
"UpstreamConfig": {
|
"chainId": 5,
|
||||||
"Enabled": true,
|
"chainName": "Goerli",
|
||||||
"URL": "https://mainnet.infura.io/v3/%INFURA_KEY%"
|
"rpcUrl": "http://goerli.blockscout.com/",
|
||||||
}
|
"blockExplorerUrl": "https://goerli.etherscan.io/",
|
||||||
}
|
"iconUrl": "network/Network=Tetnet",
|
||||||
},
|
"chainColor": "#939BA1",
|
||||||
{
|
"shortName": "goeEth",
|
||||||
"id": "xdai_rpc",
|
"nativeCurrencyName": "Ether",
|
||||||
"name": "xDai Chain",
|
"nativeCurrencySymbol": "ETH",
|
||||||
"config": {
|
"nativeCurrencyDecimals": 18,
|
||||||
"NetworkId": 100,
|
"isTest": true,
|
||||||
"DataDir": "/ethereum/xdai_rpc",
|
"layer": 1,
|
||||||
"UpstreamConfig": {
|
"enabled": false
|
||||||
"Enabled": true,
|
},
|
||||||
"URL": "https://dai.poa.network"
|
{
|
||||||
}
|
"chainId": 10,
|
||||||
}
|
"chainName": "Optimism",
|
||||||
},
|
"rpcUrl": "https://optimism-mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
{
|
"blockExplorerUrl": "https://optimistic.etherscan.io",
|
||||||
"id": "poa_rpc",
|
"iconUrl": "network/Network=Optimism",
|
||||||
"name": "POA Network",
|
"chainColor": "#E90101",
|
||||||
"config": {
|
"shortName": "opt",
|
||||||
"NetworkId": 99,
|
"nativeCurrencyName": "Ether",
|
||||||
"DataDir": "/ethereum/poa_rpc",
|
"nativeCurrencySymbol": "ETH",
|
||||||
"UpstreamConfig": { "Enabled": true, "URL": "https://core.poa.network" }
|
"nativeCurrencyDecimals": 18,
|
||||||
}
|
"isTest": false,
|
||||||
}
|
"layer": 2,
|
||||||
]
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": 69,
|
||||||
|
"chainName": "Optimism Kovan",
|
||||||
|
"rpcUrl": "https://optimism-kovan.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
|
"blockExplorerUrl": "https://kovan-optimistic.etherscan.io",
|
||||||
|
"iconUrl": "network/Network=Tetnet",
|
||||||
|
"chainColor": "#939BA1",
|
||||||
|
"shortName": "kovOpt",
|
||||||
|
"nativeCurrencyName": "Ether",
|
||||||
|
"nativeCurrencySymbol": "ETH",
|
||||||
|
"nativeCurrencyDecimals": 18,
|
||||||
|
"isTest": true,
|
||||||
|
"layer": 2,
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": 42161,
|
||||||
|
"chainName": "Arbitrum",
|
||||||
|
"rpcUrl": "https://arbitrum-mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
|
"blockExplorerUrl": "https://arbiscan.io/",
|
||||||
|
"iconUrl": "network/Network=Arbitrum",
|
||||||
|
"chainColor": "#51D0F0",
|
||||||
|
"shortName": "arb",
|
||||||
|
"nativeCurrencyName": "Ether",
|
||||||
|
"nativeCurrencySymbol": "ETH",
|
||||||
|
"nativeCurrencyDecimals": 18,
|
||||||
|
"isTest": false,
|
||||||
|
"layer": 2,
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": 421611,
|
||||||
|
"chainName": "Arbitrum Rinkeby",
|
||||||
|
"rpcUrl": "https://arbitrum-rinkeby.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
|
"blockExplorerUrl": " https://testnet.arbiscan.io",
|
||||||
|
"iconUrl": "network/Network=Tetnet",
|
||||||
|
"chainColor": "#939BA1",
|
||||||
|
"shortName": "rinArb",
|
||||||
|
"nativeCurrencyName": "Ether",
|
||||||
|
"nativeCurrencySymbol": "ETH",
|
||||||
|
"nativeCurrencyDecimals": 18,
|
||||||
|
"isTest": true,
|
||||||
|
"layer": 2,
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
|
@ -37,20 +37,26 @@
|
||||||
"MaxMessageDeliveryAttempts": 6,
|
"MaxMessageDeliveryAttempts": 6,
|
||||||
"PFSEnabled": true,
|
"PFSEnabled": true,
|
||||||
"VerifyENSContractAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
|
"VerifyENSContractAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
|
||||||
"VerifyENSURL": "https://mainnet.infura.io/v3/%INFURA_KEY%",
|
"VerifyENSURL": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"VerifyTransactionChainID": 1,
|
"VerifyTransactionChainID": 1,
|
||||||
"VerifyTransactionURL": "https://mainnet.infura.io/v3/%INFURA_KEY%",
|
"VerifyTransactionURL": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"BandwidthStatsEnabled": true
|
"BandwidthStatsEnabled": true
|
||||||
},
|
},
|
||||||
|
"Web3ProviderConfig": {
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
"EnsConfig": {
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
"StatusAccountsConfig": {
|
"StatusAccountsConfig": {
|
||||||
"Enabled": true
|
"Enabled": true
|
||||||
},
|
},
|
||||||
"UpstreamConfig": {
|
"UpstreamConfig": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"URL": "https://mainnet.infura.io/v3/%INFURA_KEY%"
|
"URL": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%"
|
||||||
},
|
},
|
||||||
"WakuConfig": {
|
"WakuConfig": {
|
||||||
"BloomFilterMode": null,
|
"BloomFilterMode": true,
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"LightClient": true,
|
"LightClient": true,
|
||||||
"MinimumPoW": 0.001
|
"MinimumPoW": 0.001
|
||||||
|
@ -59,9 +65,25 @@
|
||||||
"Enabled": false,
|
"Enabled": false,
|
||||||
"Host": "0.0.0.0",
|
"Host": "0.0.0.0",
|
||||||
"Port": 0,
|
"Port": 0,
|
||||||
"LightClient": false
|
"LightClient": false,
|
||||||
|
"PersistPeers": true,
|
||||||
|
"EnableDiscV5": true,
|
||||||
|
"UDPPort": 0,
|
||||||
|
"PeerExchange": true,
|
||||||
|
"AutoUpdate": true
|
||||||
},
|
},
|
||||||
"WalletConfig": {
|
"WalletConfig": {
|
||||||
|
"Enabled": true,
|
||||||
|
"OpenseaAPIKey": ""
|
||||||
|
},
|
||||||
|
"EnsConfig": {
|
||||||
"Enabled": true
|
"Enabled": true
|
||||||
|
},
|
||||||
|
"Networks": [],
|
||||||
|
"TorrentConfig": {
|
||||||
|
"Enabled": false,
|
||||||
|
"Port": %DEFAULT_TORRENT_CONFIG_PORT%,
|
||||||
|
"DataDir": "%DEFAULT_TORRENT_CONFIG_DATADIR%",
|
||||||
|
"TorrentDir": "%DEFAULT_TORRENT_CONFIG_TORRENTDIR%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,4 +161,117 @@ TEST(WalletApi, TestGetDerivedAddressesForPath_FromWalletAccount_SecondLevel)
|
||||||
ASSERT_TRUE(std::all_of(derivedAddresses.begin(), derivedAddresses.end(), [&testPath](const auto& a) { return a.path.get().startsWith(testPath.get()); }));
|
ASSERT_TRUE(std::all_of(derivedAddresses.begin(), derivedAddresses.end(), [&testPath](const auto& a) { return a.path.get().startsWith(testPath.get()); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(WalletApi, TestGetEthereumChains)
|
||||||
|
{
|
||||||
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
auto networks = Wallet::getEthereumChains(false);
|
||||||
|
ASSERT_GT(networks.size(), 0);
|
||||||
|
const auto &network = networks[0];
|
||||||
|
ASSERT_FALSE(network.chainName.isEmpty());
|
||||||
|
ASSERT_TRUE(network.rpcUrl.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WalletApi, TestGetTokens)
|
||||||
|
{
|
||||||
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
auto networks = Wallet::getEthereumChains(false);
|
||||||
|
ASSERT_GT(networks.size(), 0);
|
||||||
|
auto mainNetIt = std::find_if(networks.begin(), networks.end(), [](const auto &n){ return n.chainName == "Mainnet"; });
|
||||||
|
ASSERT_NE(mainNetIt, networks.end());
|
||||||
|
const auto &mainNet = *mainNetIt;
|
||||||
|
|
||||||
|
auto tokens = Wallet::getTokens(mainNet.chainId);
|
||||||
|
|
||||||
|
auto sntIt = std::find_if(tokens.begin(), tokens.end(), [](const auto &t){ return t.symbol == "SNT"; });
|
||||||
|
ASSERT_NE(sntIt, tokens.end());
|
||||||
|
const auto &snt = *sntIt;
|
||||||
|
ASSERT_EQ(snt.chainId, mainNet.chainId);
|
||||||
|
ASSERT_TRUE(snt.color.isValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WalletApi, TestGetTokensBalancesForChainIDs)
|
||||||
|
{
|
||||||
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
auto networks = Wallet::getEthereumChains(false);
|
||||||
|
ASSERT_GT(networks.size(), 1);
|
||||||
|
|
||||||
|
auto mainNetIt = std::find_if(networks.begin(), networks.end(), [](const auto &n){ return n.chainName == "Mainnet"; });
|
||||||
|
ASSERT_NE(mainNetIt, networks.end());
|
||||||
|
const auto &mainNet = *mainNetIt;
|
||||||
|
|
||||||
|
auto mainTokens = Wallet::getTokens(mainNet.chainId);
|
||||||
|
auto sntMainIt = std::find_if(mainTokens.begin(), mainTokens.end(), [](const auto &t){ return t.symbol == "SNT"; });
|
||||||
|
ASSERT_NE(sntMainIt, mainTokens.end());
|
||||||
|
const auto &sntMain = *sntMainIt;
|
||||||
|
|
||||||
|
auto testNetIt = std::find_if(networks.begin(), networks.end(), [](const auto &n){ return n.chainName == "Ropsten"; });
|
||||||
|
ASSERT_NE(testNetIt, networks.end());
|
||||||
|
const auto &testNet = *testNetIt;
|
||||||
|
|
||||||
|
auto testTokens = Wallet::getTokens(testNet.chainId);
|
||||||
|
auto sntTestIt = std::find_if(testTokens.begin(), testTokens.end(), [](const auto &t){ return t.symbol == "STT"; });
|
||||||
|
ASSERT_NE(sntTestIt, testTokens.end());
|
||||||
|
const auto &sntTest = *sntTestIt;
|
||||||
|
|
||||||
|
auto testAddress = testAccount.firstWalletAccount().address;
|
||||||
|
auto balances = Wallet::getTokensBalancesForChainIDs({mainNet.chainId, testNet.chainId},
|
||||||
|
{testAddress},
|
||||||
|
{sntMain.address, sntTest.address});
|
||||||
|
ASSERT_GT(balances.size(), 0);
|
||||||
|
|
||||||
|
ASSERT_TRUE(balances.contains(testAddress));
|
||||||
|
const auto &addressBallance = balances[testAddress];
|
||||||
|
ASSERT_GT(addressBallance.size(), 0);
|
||||||
|
|
||||||
|
ASSERT_TRUE(addressBallance.contains(sntMain.address));
|
||||||
|
ASSERT_EQ(toQString(addressBallance.at(sntMain.address)), "0");
|
||||||
|
|
||||||
|
ASSERT_TRUE(addressBallance.contains(sntTest.address));
|
||||||
|
ASSERT_EQ(toQString(addressBallance.at(sntTest.address)), "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount)
|
||||||
|
{
|
||||||
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
const auto newTestAccountName = u"test_watch_only-name"_qs;
|
||||||
|
Accounts::addAccountWatch(Accounts::EOAddress("0xdb5ac1a559b02e12f29fc0ec0e37be8e046def49"), newTestAccountName, QColor("fuchsia"), u""_qs);
|
||||||
|
const auto updatedAccounts = Accounts::getAccounts();
|
||||||
|
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||||
|
|
||||||
|
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(),
|
||||||
|
[&newTestAccountName](const auto& a) {
|
||||||
|
return a.name == newTestAccountName;
|
||||||
|
});
|
||||||
|
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||||
|
const auto &newAccount = *newAccountIt;
|
||||||
|
|
||||||
|
auto networks = Wallet::getEthereumChains(false);
|
||||||
|
ASSERT_GT(networks.size(), 1);
|
||||||
|
|
||||||
|
auto mainNetIt = std::find_if(networks.begin(), networks.end(), [](const auto &n){ return n.chainName == "Mainnet"; });
|
||||||
|
ASSERT_NE(mainNetIt, networks.end());
|
||||||
|
const auto &mainNet = *mainNetIt;
|
||||||
|
|
||||||
|
auto mainTokens = Wallet::getTokens(mainNet.chainId);
|
||||||
|
auto sntMainIt = std::find_if(mainTokens.begin(), mainTokens.end(), [](const auto &t){ return t.symbol == "SNT"; });
|
||||||
|
ASSERT_NE(sntMainIt, mainTokens.end());
|
||||||
|
const auto &sntMain = *sntMainIt;
|
||||||
|
|
||||||
|
auto balances = Wallet::getTokensBalancesForChainIDs({mainNet.chainId},
|
||||||
|
{newAccount.address},
|
||||||
|
{sntMain.address});
|
||||||
|
ASSERT_GT(balances.size(), 0);
|
||||||
|
|
||||||
|
ASSERT_TRUE(balances.contains(newAccount.address));
|
||||||
|
const auto &addressBallance = balances[newAccount.address];
|
||||||
|
ASSERT_GT(addressBallance.size(), 0);
|
||||||
|
|
||||||
|
ASSERT_TRUE(addressBallance.contains(sntMain.address));
|
||||||
|
ASSERT_GT(addressBallance.at(sntMain.address), 0);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
Loading…
Reference in New Issue