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}
|
||||
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_NAME "Status")
|
||||
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})
|
||||
|
||||
include(CTest)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## Setup dependencies
|
||||
|
||||
Note: if not stated otherwise the commands should be run from the root of the source tree
|
||||
|
||||
### 1. conancenter
|
||||
|
||||
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
|
||||
|
||||
Install latest Qt release 6.3.X
|
||||
|
||||
Platform specific conan profile
|
||||
|
||||
- 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-x86_64.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`
|
||||
- Intel: `conan install . --profile=vendor/conan-configs/apple-x86_64.ini -s build_type=Release --build=missing -if=build/conan -of=build`
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
./status-desktop
|
||||
|
@ -54,11 +57,17 @@ ctest -VV -C Release
|
|||
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
### 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 <utility>
|
||||
#include <compare>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
@ -19,25 +20,38 @@ class NamedType
|
|||
public:
|
||||
using UnderlyingType = T;
|
||||
|
||||
// constructor
|
||||
explicit constexpr NamedType(T const& value) : m_value(value) {}
|
||||
template<typename T_ = T, typename = IsNotReference<T_>>
|
||||
explicit constexpr NamedType(T&& value) : m_value(std::move(value)) {}
|
||||
explicit constexpr NamedType() = default;
|
||||
|
||||
// get
|
||||
constexpr T& get() { return m_value; }
|
||||
constexpr std::remove_reference_t<T> const& get() const {return m_value; }
|
||||
constexpr T& get() {
|
||||
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;
|
||||
bool operator>=(const NamedType<T, Parameter> &) const = default;
|
||||
bool operator==(const NamedType<T, Parameter> &) const = default;
|
||||
bool operator!=(const NamedType<T, Parameter> &) const = default;
|
||||
constexpr std::remove_reference_t<T> const& get() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
#if defined __cpp_impl_three_way_comparison && defined __cpp_lib_three_way_comparison
|
||||
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:
|
||||
T m_value;
|
||||
T m_value{};
|
||||
};
|
||||
|
||||
template <typename T, typename P>
|
||||
|
@ -59,7 +73,6 @@ template <typename T, typename Parameter>
|
|||
struct hash<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
|
||||
{
|
||||
|
@ -67,4 +80,4 @@ struct hash<Status::Helpers::NamedType<T, Parameter>>
|
|||
}
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace std
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QColor>
|
||||
#include <QUrl>
|
||||
|
||||
#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<>
|
||||
struct adl_serializer<QColor> {
|
||||
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) {
|
||||
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 <string>
|
||||
|
||||
namespace Status::Helpers {
|
||||
|
||||
constexpr bool isDebugBuild()
|
||||
|
@ -11,4 +15,18 @@ constexpr bool isDebugBuild()
|
|||
#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 QString installationId(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
||||
const QJsonObject nodeConfig(getDefaultNodeConfig(installationId));
|
||||
|
||||
const QString thumbnailImage;
|
||||
const QString largeImage;
|
||||
// TODO DEV
|
||||
const auto response = StatusGo::Accounts::login(account.name, account.keyUid, hashedPassword,
|
||||
thumbnailImage, largeImage/*, nodeConfig*/);
|
||||
thumbnailImage, largeImage);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
|
@ -310,13 +306,6 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedMul
|
|||
const QString& installationId,
|
||||
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{
|
||||
{"key-uid", account.keyUid},
|
||||
{"mnemonic", account.mnemonic},
|
||||
|
@ -332,22 +321,13 @@ QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedMul
|
|||
{"log-level", "INFO"},
|
||||
{"latest-derived-path", 0},
|
||||
{"currency", "usd"},
|
||||
//{"networks/networks", defaultNetworksJson},
|
||||
{"networks/networks", QJsonArray()},
|
||||
//{"networks/current-network", Constants::General::DefaultNetworkName},
|
||||
{"networks/current-network", ""},
|
||||
{"wallet/visible-tokens", QJsonObject()},
|
||||
//{"wallet/visible-tokens", {
|
||||
// {Constants::General::DefaultNetworkName, QJsonArray{"SNT"}}
|
||||
// }
|
||||
//},
|
||||
{"waku-enabled", true},
|
||||
{"appearance", 0},
|
||||
{"installation-id", installationId}
|
||||
};
|
||||
} catch (std::bad_optional_access) {
|
||||
return QJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
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 infuraKey = getDataFromFile(":/Status/StaticConfig/infura_key").value();
|
||||
|
||||
auto nodeConfigJsonStr = templateNodeConfigJsonStr.replace("%INSTALLATIONID%", installationId)
|
||||
.replace("%INFURA_KEY%", infuraKey);
|
||||
QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigJsonStr.toUtf8()).object();
|
||||
QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].toObject();
|
||||
auto templateDefaultNetworksJson = getDataFromFile(":/Status/StaticConfig/default-networks.json").value();
|
||||
QString defaultNetworksContent = templateDefaultNetworksJson.replace("%INFURA_TOKEN_RESOLVED%", infuraKey);
|
||||
QJsonArray defaultNetworksJson = QJsonDocument::fromJson(defaultNetworksContent.toUtf8()).array();
|
||||
|
||||
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();
|
||||
auto fleet = fleetsJson[Constants::Fleet::Prod].toObject();
|
||||
|
||||
clusterConfig["Fleet"] = Constants::Fleet::Prod;
|
||||
clusterConfig["BootNodes"] = getNodes(fleet, Constants::FleetNodes::Bootnodes);
|
||||
clusterConfig["TrustedMailServers"] = getNodes(fleet, Constants::FleetNodes::Mailservers);
|
||||
clusterConfig["StaticNodes"] = getNodes(fleet, Constants::FleetNodes::Whisper);
|
||||
clusterConfig["RendezvousNodes"] = getNodes(fleet, Constants::FleetNodes::Rendezvous);
|
||||
clusterConfig["DiscV5BootstrapNodes"] = QJsonArray();
|
||||
clusterConfig["RelayNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||
clusterConfig["StoreNodes"] = 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["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));
|
||||
return nodeConfigJson;
|
||||
} catch (std::bad_optional_access) {
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
/// \see ServiceInterface
|
||||
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;
|
||||
|
||||
void clear() override;
|
||||
|
|
|
@ -12,9 +12,6 @@ add_executable(TestOnboardingQml
|
|||
"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
|
||||
add_compile_definitions(QUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ project(StatusGoQt
|
|||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(nlohmann_json 3.10.5 REQUIRED)
|
||||
find_package(Boost)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core Concurrent Gui REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
@ -21,6 +22,7 @@ set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
|
|||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
Status::Helpers
|
||||
Boost::headers
|
||||
|
||||
PRIVATE
|
||||
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.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/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.cpp
|
||||
)
|
||||
|
|
|
@ -22,8 +22,9 @@ const char* statusGoCallPrivateRPC(const char* inputJSON) {
|
|||
|
||||
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(),
|
||||
QCryptographicHash::Keccak_256).toHex()));
|
||||
QCryptographicHash::Keccak_256).toHex().toUpper()));
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
/*!
|
||||
* \brief Define a derived address as returned by the corresponding API
|
||||
* \note equivalent of status-go's DerivedAddress@api.go
|
||||
* \see \c getDerivedAddressesForPath
|
||||
*/
|
||||
/// \brief Define a derived address as returned by the corresponding API
|
||||
/// \note equivalent of status-go's DerivedAddress@api.go
|
||||
/// \see \c getDerivedAddressesForPath
|
||||
struct DerivedAddress
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -2,7 +2,12 @@
|
|||
|
||||
#include "Accounts/ChatOrWalletAccount.h"
|
||||
#include "Accounts/accounts_types.h"
|
||||
#include "BigInt.h"
|
||||
|
||||
#include "DerivedAddress.h"
|
||||
#include "NetworkConfiguration.h"
|
||||
#include "Token.h"
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
#include <vector>
|
||||
|
@ -16,4 +21,25 @@ namespace Status::StatusGo::Wallet
|
|||
/// \throws \c CallPrivateRpcError
|
||||
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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
NewWalletAccountController(std::shared_ptr<AccountsModel> accounts);
|
||||
~NewWalletAccountController();
|
||||
|
||||
/// Called by QML engine to register the instance. QML takes ownership of the instance
|
||||
static NewWalletAccountController *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
||||
|
|
|
@ -9,6 +9,6 @@ Item {
|
|||
|
||||
Label {
|
||||
anchors.centerIn: parent
|
||||
text: "$$$$$"
|
||||
text: asset.name
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,6 @@ NewWalletAccountController::NewWalletAccountController(std::shared_ptr<Helpers::
|
|||
{
|
||||
}
|
||||
|
||||
NewWalletAccountController::~NewWalletAccountController()
|
||||
{
|
||||
}
|
||||
|
||||
QAbstractListModel* NewWalletAccountController::mainAccountsModel()
|
||||
{
|
||||
|
@ -58,10 +55,10 @@ void NewWalletAccountController::setDerivationPath(const QString &newDerivationP
|
|||
emit derivationPathChanged();
|
||||
|
||||
auto oldCustom = m_customDerivationPath;
|
||||
auto found = searchDerivationPath(m_derivationPath);
|
||||
m_customDerivationPath = std::get<0>(found) == nullptr;
|
||||
if(!m_customDerivationPath && !std::get<0>(found).get()->alreadyCreated())
|
||||
updateSelectedDerivedAddress(std::get<1>(found), std::get<0>(found));
|
||||
const auto &[derivedPath, index]= searchDerivationPath(m_derivationPath);
|
||||
m_customDerivationPath = derivedPath == nullptr;
|
||||
if(!m_customDerivationPath && !derivedPath.get()->alreadyCreated())
|
||||
updateSelectedDerivedAddress(index, derivedPath);
|
||||
|
||||
if(m_customDerivationPath != oldCustom)
|
||||
emit customDerivationPathChanged();
|
||||
|
|
|
@ -1,76 +1,122 @@
|
|||
[
|
||||
{
|
||||
"id": "testnet_rpc",
|
||||
"etherscan-link": "https://ropsten.etherscan.io/address/",
|
||||
"name": "Ropsten with upstream RPC",
|
||||
"config": {
|
||||
"NetworkId": 3,
|
||||
"DataDir": "/ethereum/testnet_rpc",
|
||||
"UpstreamConfig": {
|
||||
"Enabled": true,
|
||||
"URL": "https://ropsten.infura.io/v3/%INFURA_KEY%"
|
||||
}
|
||||
}
|
||||
"chainId": 1,
|
||||
"chainName": "Mainnet",
|
||||
"rpcUrl": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||
"blockExplorerUrl": "https://etherscan.io/",
|
||||
"iconUrl": "network/Network=Ethereum",
|
||||
"chainColor": "#627EEA",
|
||||
"shortName": "eth",
|
||||
"nativeCurrencyName": "Ether",
|
||||
"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",
|
||||
"config": {
|
||||
"NetworkId": 4,
|
||||
"DataDir": "/ethereum/rinkeby_rpc",
|
||||
"UpstreamConfig": {
|
||||
"Enabled": true,
|
||||
"URL": "https://rinkeby.infura.io/v3/%INFURA_KEY%"
|
||||
}
|
||||
}
|
||||
"chainId": 3,
|
||||
"chainName": "Ropsten",
|
||||
"rpcUrl": "https://ropsten.infura.io/v3%INFURA_TOKEN_RESOLVED%",
|
||||
"blockExplorerUrl": "https://ropsten.etherscan.io/",
|
||||
"iconUrl": "network/Network=Tetnet",
|
||||
"chainColor": "#939BA1",
|
||||
"shortName": "ropEth",
|
||||
"nativeCurrencyName": "Ether",
|
||||
"nativeCurrencySymbol": "ETH",
|
||||
"nativeCurrencyDecimals": 18,
|
||||
"isTest": true,
|
||||
"layer": 1,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "goerli_rpc",
|
||||
"etherscan-link": "https://goerli.etherscan.io/address/",
|
||||
"name": "Goerli with upstream RPC",
|
||||
"config": {
|
||||
"NetworkId": 5,
|
||||
"DataDir": "/ethereum/goerli_rpc",
|
||||
"UpstreamConfig": {
|
||||
"Enabled": true,
|
||||
"URL": "https://goerli.blockscout.com/"
|
||||
}
|
||||
}
|
||||
"chainId": 4,
|
||||
"chainName": "Rinkeby",
|
||||
"rpcUrl": "https://rinkeby.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||
"blockExplorerUrl": "https://rinkeby.etherscan.io/",
|
||||
"iconUrl": "network/Network=Tetnet",
|
||||
"chainColor": "#939BA1",
|
||||
"shortName": "rinEth",
|
||||
"nativeCurrencyName": "Ether",
|
||||
"nativeCurrencySymbol": "ETH",
|
||||
"nativeCurrencyDecimals": 18,
|
||||
"isTest": true,
|
||||
"layer": 1,
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": "mainnet_rpc",
|
||||
"etherscan-link": "https://etherscan.io/address/",
|
||||
"name": "Mainnet with upstream RPC",
|
||||
"config": {
|
||||
"NetworkId": 1,
|
||||
"DataDir": "/ethereum/mainnet_rpc",
|
||||
"UpstreamConfig": {
|
||||
"Enabled": true,
|
||||
"URL": "https://mainnet.infura.io/v3/%INFURA_KEY%"
|
||||
}
|
||||
}
|
||||
"chainId": 5,
|
||||
"chainName": "Goerli",
|
||||
"rpcUrl": "http://goerli.blockscout.com/",
|
||||
"blockExplorerUrl": "https://goerli.etherscan.io/",
|
||||
"iconUrl": "network/Network=Tetnet",
|
||||
"chainColor": "#939BA1",
|
||||
"shortName": "goeEth",
|
||||
"nativeCurrencyName": "Ether",
|
||||
"nativeCurrencySymbol": "ETH",
|
||||
"nativeCurrencyDecimals": 18,
|
||||
"isTest": true,
|
||||
"layer": 1,
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"id": "xdai_rpc",
|
||||
"name": "xDai Chain",
|
||||
"config": {
|
||||
"NetworkId": 100,
|
||||
"DataDir": "/ethereum/xdai_rpc",
|
||||
"UpstreamConfig": {
|
||||
"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",
|
||||
"iconUrl": "network/Network=Optimism",
|
||||
"chainColor": "#E90101",
|
||||
"shortName": "opt",
|
||||
"nativeCurrencyName": "Ether",
|
||||
"nativeCurrencySymbol": "ETH",
|
||||
"nativeCurrencyDecimals": 18,
|
||||
"isTest": false,
|
||||
"layer": 2,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "poa_rpc",
|
||||
"name": "POA Network",
|
||||
"config": {
|
||||
"NetworkId": 99,
|
||||
"DataDir": "/ethereum/poa_rpc",
|
||||
"UpstreamConfig": { "Enabled": true, "URL": "https://core.poa.network" }
|
||||
}
|
||||
"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,
|
||||
"PFSEnabled": true,
|
||||
"VerifyENSContractAddress": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
|
||||
"VerifyENSURL": "https://mainnet.infura.io/v3/%INFURA_KEY%",
|
||||
"VerifyENSURL": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||
"VerifyTransactionChainID": 1,
|
||||
"VerifyTransactionURL": "https://mainnet.infura.io/v3/%INFURA_KEY%",
|
||||
"VerifyTransactionURL": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||
"BandwidthStatsEnabled": true
|
||||
},
|
||||
"Web3ProviderConfig": {
|
||||
"Enabled": true
|
||||
},
|
||||
"EnsConfig": {
|
||||
"Enabled": true
|
||||
},
|
||||
"StatusAccountsConfig": {
|
||||
"Enabled": true
|
||||
},
|
||||
"UpstreamConfig": {
|
||||
"Enabled": true,
|
||||
"URL": "https://mainnet.infura.io/v3/%INFURA_KEY%"
|
||||
"URL": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%"
|
||||
},
|
||||
"WakuConfig": {
|
||||
"BloomFilterMode": null,
|
||||
"BloomFilterMode": true,
|
||||
"Enabled": true,
|
||||
"LightClient": true,
|
||||
"MinimumPoW": 0.001
|
||||
|
@ -59,9 +65,25 @@
|
|||
"Enabled": false,
|
||||
"Host": "0.0.0.0",
|
||||
"Port": 0,
|
||||
"LightClient": false
|
||||
"LightClient": false,
|
||||
"PersistPeers": true,
|
||||
"EnableDiscV5": true,
|
||||
"UDPPort": 0,
|
||||
"PeerExchange": true,
|
||||
"AutoUpdate": true
|
||||
},
|
||||
"WalletConfig": {
|
||||
"Enabled": true,
|
||||
"OpenseaAPIKey": ""
|
||||
},
|
||||
"EnsConfig": {
|
||||
"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()); }));
|
||||
}
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue