feat(Wallet) cache fetched balance history to DB for efficiency
- Bump status-go head that include the required specific changes - fetch token balance (native or ERC20) and cache historical token quantity data - fetch FIAT currency - Extend presentation layer (NIM and QML) to account for API changes - Remove timed request and other optimizations from the time of fetching balance history every time instead of querying cache - Add C++ integration debugging tests and update network chain configuration (outdated) Closes: #8175
This commit is contained in:
parent
7ebe5488bd
commit
3bb667bb7a
|
@ -60,8 +60,13 @@ ctest --test-dir ./build
|
||||||
|
|
||||||
### Build with QtCreator
|
### Build with QtCreator
|
||||||
|
|
||||||
If go is installed with brew use the following configuration otherwise adapt the configuration.
|
Go to QtCreator's preferences navigate to Environment -> System -> Environment -> Change add GOBIN to the PATH
|
||||||
Go to QtCreator's preferences navigate to Environment -> System -> Environment -> Change and paste
|
|
||||||
|
### MacOS instructions
|
||||||
|
|
||||||
|
If go is installed with `brew` use the following configuration otherwise adapt it to your environment. Also this will allow access to conan if installed by brew
|
||||||
|
|
||||||
|
Use this in the Environment section of the QtCreator's preferences:
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
GOBIN=${GOPATH}/bin
|
GOBIN=${GOPATH}/bin
|
||||||
|
|
|
@ -359,6 +359,7 @@ QJsonArray getNodes(const QJsonObject& fleet, const QString& nodeType)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mirrors and in sync with getDefaultNodeConfig@service.nim
|
||||||
QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId) const
|
QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId) const
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -391,22 +392,14 @@ QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId)
|
||||||
|
|
||||||
QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigJsonStr.toUtf8()).object();
|
QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigJsonStr.toUtf8()).object();
|
||||||
|
|
||||||
QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].toObject();
|
auto clusterConfig = nodeConfigJson["ClusterConfig"].toObject();
|
||||||
QJsonObject fleetsJson = QJsonDocument::fromJson(fleetJson.toUtf8()).object()["fleets"].toObject();
|
auto 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["StoreNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
|
||||||
clusterConfig["FilterNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
|
||||||
clusterConfig["LightpushNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
|
||||||
|
|
||||||
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
|
||||||
|
|
||||||
nodeConfigJson["NetworkId"] = defaultNetworksJson[0].toObject()["chainId"];
|
nodeConfigJson["NetworkId"] = defaultNetworksJson[0].toObject()["chainId"];
|
||||||
nodeConfigJson["DataDir"] = "ethereum";
|
nodeConfigJson["DataDir"] = "ethereum";
|
||||||
auto upstreamConfig = nodeConfigJson["UpstreamConfig"].toObject();
|
auto upstreamConfig = nodeConfigJson["UpstreamConfig"].toObject();
|
||||||
|
@ -417,6 +410,27 @@ QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId)
|
||||||
shhextConfig["InstallationID"] = installationId;
|
shhextConfig["InstallationID"] = installationId;
|
||||||
nodeConfigJson["ShhextConfig"] = shhextConfig;
|
nodeConfigJson["ShhextConfig"] = shhextConfig;
|
||||||
nodeConfigJson["Networks"] = defaultNetworksJson;
|
nodeConfigJson["Networks"] = defaultNetworksJson;
|
||||||
|
nodeConfigJson["NoDiscovery"] = true;
|
||||||
|
nodeConfigJson["Rendezvous"] = false;
|
||||||
|
QJsonArray dnsDiscoveryURL = {"enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@prod.nodes.status.im"};
|
||||||
|
clusterConfig["WakuNodes"] = dnsDiscoveryURL;
|
||||||
|
clusterConfig["DiscV5BootstrapNodes"] = dnsDiscoveryURL;
|
||||||
|
|
||||||
|
auto wakuv2Config = nodeConfigJson["WakuV2Config"].toObject();
|
||||||
|
wakuv2Config["EnableDiscV5"] = true;
|
||||||
|
wakuv2Config["DiscoveryLimit"] = 20;
|
||||||
|
wakuv2Config["Rendezvous"] = true;
|
||||||
|
wakuv2Config["Enabled"] = true;
|
||||||
|
|
||||||
|
wakuv2Config["Enabled"] = false;
|
||||||
|
nodeConfigJson["WakuV2Config"] = wakuv2Config;
|
||||||
|
|
||||||
|
clusterConfig["RelayNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
|
clusterConfig["StoreNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
|
clusterConfig["FilterNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
|
clusterConfig["LightpushNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||||
|
|
||||||
|
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
||||||
|
|
||||||
nodeConfigJson["KeyStoreDir"] = toQString(fs::relative(m_keyStoreDir, m_statusgoDataDir));
|
nodeConfigJson["KeyStoreDir"] = toQString(fs::relative(m_keyStoreDir, m_statusgoDataDir));
|
||||||
return nodeConfigJson;
|
return nodeConfigJson;
|
||||||
|
|
|
@ -9,4 +9,9 @@ const EventType Events::RecentHistoryReady{"recent-history-ready"};
|
||||||
const EventType Events::FetchingHistoryError{"fetching-history-error"};
|
const EventType Events::FetchingHistoryError{"fetching-history-error"};
|
||||||
const EventType Events::NonArchivalNodeDetected{"non-archival-node-detected"};
|
const EventType Events::NonArchivalNodeDetected{"non-archival-node-detected"};
|
||||||
|
|
||||||
} // namespace Status::StatusGo::Wallet
|
const EventType Events::WalletTickReload{"wallet-tick-reload"};
|
||||||
|
const EventType Events::EventBalanceHistoryUpdateStarted{"wallet-balance-history-update-started"};
|
||||||
|
const EventType Events::EventBalanceHistoryUpdateFinished{"wallet-balance-history-update-finished"};
|
||||||
|
const EventType Events::EventBalanceHistoryUpdateFinishedWithError{"wallet-balance-history-update-finished-with-error"};
|
||||||
|
|
||||||
|
} // namespace Status::StatusGo::Wallet::Transfer
|
||||||
|
|
|
@ -22,6 +22,11 @@ struct Events
|
||||||
static const EventType RecentHistoryReady;
|
static const EventType RecentHistoryReady;
|
||||||
static const EventType FetchingHistoryError;
|
static const EventType FetchingHistoryError;
|
||||||
static const EventType NonArchivalNodeDetected;
|
static const EventType NonArchivalNodeDetected;
|
||||||
|
|
||||||
|
static const EventType WalletTickReload;
|
||||||
|
static const EventType EventBalanceHistoryUpdateStarted;
|
||||||
|
static const EventType EventBalanceHistoryUpdateFinished;
|
||||||
|
static const EventType EventBalanceHistoryUpdateFinishedWithError;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \see status-go's Event@events.go in services/wallet/transfer module
|
/// \see status-go's Event@events.go in services/wallet/transfer module
|
||||||
|
@ -36,4 +41,4 @@ struct Event
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Event, type, blockNumber, accounts, message);
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Event, type, blockNumber, accounts, message);
|
||||||
|
|
||||||
} // namespace Status
|
} // namespace Status::StatusGo::Wallet::Transfer
|
||||||
|
|
|
@ -108,10 +108,12 @@ TokenBalances getTokensBalancesForChainIDs(const std::vector<ChainID>& chainIds,
|
||||||
return resultData;
|
return resultData;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TokenBalanceHistory>
|
std::vector<TokenBalanceHistory> getBalanceHistory(const std::vector<ChainID>& chainIds,
|
||||||
getBalanceHistory(const ChainID& chainID, Accounts::EOAddress account, BalanceHistoryTimeInterval timeInterval)
|
Accounts::EOAddress account,
|
||||||
|
const QString& currency,
|
||||||
|
BalanceHistoryTimeInterval timeInterval)
|
||||||
{
|
{
|
||||||
std::vector<json> params = {chainID, account, timeInterval};
|
std::vector<json> params = {chainIds, account, currency, timeInterval};
|
||||||
json inputJson = {{"jsonrpc", "2.0"}, {"method", "wallet_getBalanceHistory"}, {"params", params}};
|
json inputJson = {{"jsonrpc", "2.0"}, {"method", "wallet_getBalanceHistory"}, {"params", params}};
|
||||||
|
|
||||||
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
@ -121,6 +123,18 @@ getBalanceHistory(const ChainID& chainID, Accounts::EOAddress account, BalanceHi
|
||||||
return resultJson.get<CallPrivateRpcResponse>().result;
|
return resultJson.get<CallPrivateRpcResponse>().result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool updateBalanceHistoryForAllEnabledNetworks()
|
||||||
|
{
|
||||||
|
json inputJson = {
|
||||||
|
{"jsonrpc", "2.0"}, {"method", "wallet_updateBalanceHistoryForAllEnabledNetworks"}, {"params", {}}};
|
||||||
|
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
|
||||||
|
return resultJson.get<CallPrivateRpcResponse>().result;
|
||||||
|
}
|
||||||
|
|
||||||
void checkRecentHistory(const std::vector<Accounts::EOAddress>& accounts)
|
void checkRecentHistory(const std::vector<Accounts::EOAddress>& accounts)
|
||||||
{
|
{
|
||||||
std::vector<json> params = {accounts};
|
std::vector<json> params = {accounts};
|
||||||
|
@ -130,4 +144,29 @@ void checkRecentHistory(const std::vector<Accounts::EOAddress>& accounts)
|
||||||
checkPrivateRpcCallResultAndReportError(resultJson);
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startWallet()
|
||||||
|
{
|
||||||
|
json inputJson = {{"jsonrpc", "2.0"}, {"method", "wallet_startWallet"}, {"params", {}}};
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopWallet()
|
||||||
|
{
|
||||||
|
json inputJson = {{"jsonrpc", "2.0"}, {"method", "wallet_stopWallet"}, {"params", {}}};
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateVisibleTokens(std::vector<QString> symbols)
|
||||||
|
{
|
||||||
|
json inputJson = {
|
||||||
|
{"jsonrpc", "2.0"}, {"method", "wallet_updateVisibleTokens"}, {"params", std::vector<json>{symbols}}};
|
||||||
|
auto result = Utils::statusGoCallPrivateRPC(inputJson.dump().c_str());
|
||||||
|
const auto resultJson = json::parse(result);
|
||||||
|
checkPrivateRpcCallResultAndReportError(resultJson);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Status::StatusGo::Wallet
|
} // namespace Status::StatusGo::Wallet
|
||||||
|
|
|
@ -66,9 +66,10 @@ struct TokenBalanceHistory
|
||||||
{
|
{
|
||||||
BigInt value;
|
BigInt value;
|
||||||
QDateTime time;
|
QDateTime time;
|
||||||
|
BigInt blockNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TokenBalanceHistory, value, time)
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TokenBalanceHistory, value, time, blockNumber)
|
||||||
|
|
||||||
/// @see status-go's services/wallet/transfer/controller.go BalanceHistoryTimeInterval
|
/// @see status-go's services/wallet/transfer/controller.go BalanceHistoryTimeInterval
|
||||||
enum BalanceHistoryTimeInterval
|
enum BalanceHistoryTimeInterval
|
||||||
|
@ -86,11 +87,30 @@ enum BalanceHistoryTimeInterval
|
||||||
/// \see checkRecentHistory
|
/// \see checkRecentHistory
|
||||||
/// \note status-go's API -> GetBalanceHistory@api.go
|
/// \note status-go's API -> GetBalanceHistory@api.go
|
||||||
/// \throws \c CallPrivateRpcError
|
/// \throws \c CallPrivateRpcError
|
||||||
std::vector<TokenBalanceHistory>
|
std::vector<TokenBalanceHistory> getBalanceHistory(const std::vector<ChainID>& chainIds,
|
||||||
getBalanceHistory(const ChainID& chainID, Accounts::EOAddress account, BalanceHistoryTimeInterval timeInterval);
|
Accounts::EOAddress account,
|
||||||
|
const QString& currency,
|
||||||
|
BalanceHistoryTimeInterval timeInterval);
|
||||||
|
|
||||||
|
/// \note status-go's API -> updateBalanceHistoryForAllEnabledNetworks@api.go
|
||||||
|
///
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
bool updateBalanceHistoryForAllEnabledNetworks();
|
||||||
|
|
||||||
/// \note status-go's API -> CheckRecentHistory@api.go
|
/// \note status-go's API -> CheckRecentHistory@api.go
|
||||||
/// \throws \c CallPrivateRpcError
|
/// \throws \c CallPrivateRpcError
|
||||||
void checkRecentHistory(const std::vector<Accounts::EOAddress>& accounts);
|
void checkRecentHistory(const std::vector<Accounts::EOAddress>& accounts);
|
||||||
|
|
||||||
|
/// \note status-go's API -> StartWallet@api.go
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
void startWallet();
|
||||||
|
|
||||||
|
/// \note status-go's API -> StopWallet@api.go
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
void stopWallet();
|
||||||
|
|
||||||
|
/// \note status-go's API -> UpdateVisibleTokens@api.go
|
||||||
|
/// \throws \c CallPrivateRpcError
|
||||||
|
void updateVisibleTokens(std::vector<QString> symbols);
|
||||||
|
|
||||||
} // namespace Status::StatusGo::Wallet
|
} // namespace Status::StatusGo::Wallet
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"chainId": 1,
|
"chainId": 1,
|
||||||
"chainName": "Mainnet",
|
"chainName": "Ethereum Mainnet",
|
||||||
"rpcUrl": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
"rpcUrl": "https://mainnet.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"blockExplorerUrl": "https://etherscan.io/",
|
"blockExplorerUrl": "https://etherscan.io/",
|
||||||
"iconUrl": "network/Network=Ethereum",
|
"iconUrl": "network/Network=Ethereum",
|
||||||
|
@ -14,42 +14,12 @@
|
||||||
"layer": 1,
|
"layer": 1,
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"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": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"chainId": 5,
|
"chainId": 5,
|
||||||
"chainName": "Goerli",
|
"chainName": "Goerli",
|
||||||
"rpcUrl": "http://goerli.blockscout.com/",
|
"rpcUrl": "https://goerli.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"blockExplorerUrl": "https://goerli.etherscan.io/",
|
"blockExplorerUrl": "https://goerli.etherscan.io/",
|
||||||
"iconUrl": "network/Network=Tetnet",
|
"iconUrl": "network/Network=Testnet",
|
||||||
"chainColor": "#939BA1",
|
"chainColor": "#939BA1",
|
||||||
"shortName": "goeEth",
|
"shortName": "goeEth",
|
||||||
"nativeCurrencyName": "Ether",
|
"nativeCurrencyName": "Ether",
|
||||||
|
@ -75,13 +45,13 @@
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"chainId": 69,
|
"chainId": 420,
|
||||||
"chainName": "Optimism Kovan",
|
"chainName": "Optimism Goerli Testnet",
|
||||||
"rpcUrl": "https://optimism-kovan.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
"rpcUrl": "https://optimism-goerli.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"blockExplorerUrl": "https://kovan-optimistic.etherscan.io",
|
"blockExplorerUrl": "https://goerli-optimism.etherscan.io/",
|
||||||
"iconUrl": "network/Network=Tetnet",
|
"iconUrl": "network/Network=Testnet",
|
||||||
"chainColor": "#939BA1",
|
"chainColor": "#939BA1",
|
||||||
"shortName": "kovOpt",
|
"shortName": "goerOpt",
|
||||||
"nativeCurrencyName": "Ether",
|
"nativeCurrencyName": "Ether",
|
||||||
"nativeCurrencySymbol": "ETH",
|
"nativeCurrencySymbol": "ETH",
|
||||||
"nativeCurrencyDecimals": 18,
|
"nativeCurrencyDecimals": 18,
|
||||||
|
@ -105,11 +75,11 @@
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"chainId": 421611,
|
"chainId": 421613,
|
||||||
"chainName": "Arbitrum Rinkeby",
|
"chainName": "Arbitrum Goerli",
|
||||||
"rpcUrl": "https://arbitrum-rinkeby.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
"rpcUrl": "https://arbitrum-goerli.infura.io/v3/%INFURA_TOKEN_RESOLVED%",
|
||||||
"blockExplorerUrl": " https://testnet.arbiscan.io",
|
"blockExplorerUrl": "https://goerli.arbiscan.io/",
|
||||||
"iconUrl": "network/Network=Tetnet",
|
"iconUrl": "network/Network=Testnet",
|
||||||
"chainColor": "#939BA1",
|
"chainColor": "#939BA1",
|
||||||
"shortName": "rinArb",
|
"shortName": "rinArb",
|
||||||
"nativeCurrencyName": "Ether",
|
"nativeCurrencyName": "Ether",
|
||||||
|
|
|
@ -111,6 +111,35 @@
|
||||||
"node-01.gc-us-central1-a.go-waku.test": "/dns4/node-01.gc-us-central1-a.go-waku.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPz63Xc6AuVkDeujz7YeZta18rcdau3Y1BzaxKAfDrBqz"
|
"node-01.gc-us-central1-a.go-waku.test": "/dns4/node-01.gc-us-central1-a.go-waku.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPz63Xc6AuVkDeujz7YeZta18rcdau3Y1BzaxKAfDrBqz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"status.prod": {
|
||||||
|
"tcp/p2p/waku": {
|
||||||
|
"node-01.ac-cn-hongkong-c.status.prod": "/dns4/node-01.ac-cn-hongkong-c.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAkvEZgh3KLwhLwXg95e5ojM8XykJ4Kxi2T7hk22rnA7pJC",
|
||||||
|
"node-01.do-ams3.status.prod": "/dns4/node-01.do-ams3.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAm6HZZr7aToTvEBPpiys4UxajCTU97zj5v7RNR2gbniy1D",
|
||||||
|
"node-01.gc-us-central1-a.status.prod": "/dns4/node-01.gc-us-central1-a.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAkwBp8T6G77kQXSNMnxgaMky1JeyML5yqoTHRM8dbeCBNb",
|
||||||
|
"node-02.ac-cn-hongkong-c.status.prod": "/dns4/node-02.ac-cn-hongkong-c.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmFy8BrJhCEmCYrUfBdSNkrPw6VHExtv4rRp1DSBnCPgx8",
|
||||||
|
"node-02.do-ams3.status.prod": "/dns4/node-02.do-ams3.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmSve7tR5YZugpskMv2dmJAsMUKmfWYEKRXNUxRaTCnsXV",
|
||||||
|
"node-02.gc-us-central1-a.status.prod": "/dns4/node-02.gc-us-central1-a.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmDQugwDHM3YeUp86iGjrUvbdw3JPRgikC7YoGBsT2ymMg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status.test": {
|
||||||
|
"tcp/p2p/waku": {
|
||||||
|
"node-01.ac-cn-hongkong-c.status.test": "/dns4/node-01.ac-cn-hongkong-c.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAm2BjXxCp1sYFJQKpLLbPbwd5juxbsYofu3TsS3auvT9Yi",
|
||||||
|
"node-01.do-ams3.status.test": "/dns4/node-01.do-ams3.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAkukebeXjTQ9QDBeNDWuGfbaSg79wkkhK4vPocLgR6QFDf",
|
||||||
|
"node-01.gc-us-central1-a.status.test": "/dns4/node-01.gc-us-central1-a.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAmGDX3iAFox93PupVYaHa88kULGqMpJ7AEHGwj3jbMtt76"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"waku.connect": {
|
||||||
|
"tcp/p2p/waku": {
|
||||||
|
"nim-01.ac-cn-hongkong-c.waku.connect": "/ip4/47.242.185.35/tcp/30303/p2p/16Uiu2HAm75XUMGev2Ti74G3wUzhyxCtbaDKVWzNwbq3tn5WfzRd4",
|
||||||
|
"nim-01.do-ams3.waku.connect": "/ip4/206.189.242.0/tcp/30303/p2p/16Uiu2HAm9VLETt1xBwDAwfKxj2XvAZDw73Bn4HQf11U26JGDxqZD",
|
||||||
|
"nim-01.gc-us-central1-a.waku.connect": "/ip4/35.193.87.35/tcp/30303/p2p/16Uiu2HAmMi8xaj9W22a67shGg5wtw1nZDNtfrTPHkgKA5Uhvnvbn"
|
||||||
|
},
|
||||||
|
"wss/p2p/waku": {
|
||||||
|
"nim-01.ac-cn-hongkong-c.waku.connect": "/dns4/nim-01.ac-cn-hongkong-c.waku.connect.statusim.net/tcp/443/wss/p2p/16Uiu2HAm75XUMGev2Ti74G3wUzhyxCtbaDKVWzNwbq3tn5WfzRd4",
|
||||||
|
"nim-01.do-ams3.waku.connect": "/dns4/nim-01.do-ams3.waku.connect.statusim.net/tcp/443/wss/p2p/16Uiu2HAm9VLETt1xBwDAwfKxj2XvAZDw73Bn4HQf11U26JGDxqZD",
|
||||||
|
"nim-01.gc-us-central1-a.waku.connect": "/dns4/nim-01.gc-us-central1-a.waku.connect.statusim.net/tcp/443/wss/p2p/16Uiu2HAmMi8xaj9W22a67shGg5wtw1nZDNtfrTPHkgKA5Uhvnvbn"
|
||||||
|
}
|
||||||
|
},
|
||||||
"wakuv2.prod": {
|
"wakuv2.prod": {
|
||||||
"waku": {
|
"waku": {
|
||||||
"node-01.ac-cn-hongkong-c.wakuv2.prod": "/ip4/8.210.222.231/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD",
|
"node-01.ac-cn-hongkong-c.wakuv2.prod": "/ip4/8.210.222.231/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD",
|
||||||
|
@ -125,19 +154,19 @@
|
||||||
},
|
},
|
||||||
"wakuv2.test": {
|
"wakuv2.test": {
|
||||||
"waku": {
|
"waku": {
|
||||||
"node-01.ac-cn-hongkong-c.wakuv2.test": "/ip4/47.242.210.73/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
|
"node-01.ac-cn-hongkong-c.wakuv2.test": "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
|
||||||
"node-01.do-ams3.wakuv2.test": "/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
|
"node-01.do-ams3.wakuv2.test": "/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
|
||||||
"node-01.gc-us-central1-a.wakuv2.test": "/ip4/104.154.239.128/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
|
"node-01.gc-us-central1-a.wakuv2.test": "/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
|
||||||
},
|
},
|
||||||
"waku-websocket": {
|
"waku-websocket": {
|
||||||
"node-01.ac-cn-hongkong-c.wakuv2.test": "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
|
"node-01.ac-cn-hongkong-c.wakuv2.test": "/dns4/node-01.ac-cn-hongkong-c.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
|
||||||
"node-01.do-ams3.wakuv2.test": "/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
|
"node-01.do-ams3.wakuv2.test": "/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
|
||||||
"node-01.gc-us-central1-a.wakuv2.test": "/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
|
"node-01.gc-us-central1-a.wakuv2.test": "/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"hostname": "node-01.do-ams3.sites.misc",
|
"hostname": "node-01.do-ams3.sites.misc",
|
||||||
"timestamp": "2021-10-19T00:00:15.465044"
|
"timestamp": "2022-03-10T11:32:40.427357",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"KeyStoreDir": "./keystore",
|
"KeyStoreDir": "./keystore",
|
||||||
"IPFSDir": "./ipfs",
|
"IPFSDir": "./ipfs",
|
||||||
"LogEnabled": true,
|
"LogEnabled": true,
|
||||||
"LogFile": "./geth.log",
|
"LogFile": "geth.log",
|
||||||
"LogLevel": "INFO",
|
"LogLevel": "INFO",
|
||||||
"MailserversConfig": {
|
"MailserversConfig": {
|
||||||
"Enabled": true
|
"Enabled": true
|
||||||
|
@ -76,9 +76,6 @@
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"OpenseaAPIKey": ""
|
"OpenseaAPIKey": ""
|
||||||
},
|
},
|
||||||
"EnsConfig": {
|
|
||||||
"Enabled": true
|
|
||||||
},
|
|
||||||
"Networks": [],
|
"Networks": [],
|
||||||
"TorrentConfig": {
|
"TorrentConfig": {
|
||||||
"Enabled": false,
|
"Enabled": false,
|
||||||
|
|
|
@ -33,11 +33,15 @@ proc init*(self: Controller) =
|
||||||
let args = TokenHistoricalDataArgs(e)
|
let args = TokenHistoricalDataArgs(e)
|
||||||
self.delegate.tokenHistoricalDataResolved(args.result)
|
self.delegate.tokenHistoricalDataResolved(args.result)
|
||||||
|
|
||||||
|
self.events.on(SIGNAL_BALANCE_HISTORY_DATA_READY) do(e:Args):
|
||||||
|
let args = TokenBalanceHistoryDataArgs(e)
|
||||||
|
self.delegate.tokenBalanceHistoryDataResolved(args.result)
|
||||||
|
|
||||||
method findTokenSymbolByAddress*(self: Controller, address: string): string =
|
method findTokenSymbolByAddress*(self: Controller, address: string): string =
|
||||||
return self.walletAccountService.findTokenSymbolByAddress(address)
|
return self.walletAccountService.findTokenSymbolByAddress(address)
|
||||||
|
|
||||||
method getHistoricalDataForToken*(self: Controller, symbol: string, currency: string, range: int) =
|
method getHistoricalDataForToken*(self: Controller, symbol: string, currency: string, range: int) =
|
||||||
self.tokenService.getHistoricalDataForToken(symbol, currency, range)
|
self.tokenService.getHistoricalDataForToken(symbol, currency, range)
|
||||||
|
|
||||||
method fetchHistoricalBalanceForTokenAsJson*(self: Controller, address: string, symbol: string, timeIntervalEnum: int) =
|
method fetchHistoricalBalanceForTokenAsJson*(self: Controller, address: string, tokenSymbol: string, currencySymbol: string, timeIntervalEnum: int) =
|
||||||
self.tokenService.fetchHistoricalBalanceForTokenAsJson(address, symbol, BalanceHistoryTimeInterval(timeIntervalEnum))
|
self.tokenService.fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol, BalanceHistoryTimeInterval(timeIntervalEnum))
|
|
@ -20,7 +20,7 @@ method getHistoricalDataForToken*(self: AccessInterface, symbol: string, currenc
|
||||||
method tokenHistoricalDataResolved*(self: AccessInterface, tokenDetails: string) {.base.} =
|
method tokenHistoricalDataResolved*(self: AccessInterface, tokenDetails: string) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
method fetchHistoricalBalanceForTokenAsJson*(self: AccessInterface, address: string, symbol: string, timeIntervalEnum: int) {.base.} =
|
method fetchHistoricalBalanceForTokenAsJson*(self: AccessInterface, address: string, tokenSymbol: string, currencySymbol: string, timeIntervalEnum: int) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
method tokenBalanceHistoryDataResolved*(self: AccessInterface, balanceHistoryJson: string) {.base.} =
|
method tokenBalanceHistoryDataResolved*(self: AccessInterface, balanceHistoryJson: string) {.base.} =
|
||||||
|
|
|
@ -63,8 +63,8 @@ method tokenHistoricalDataResolved*(self: Module, tokenDetails: string) =
|
||||||
self.view.setTokenHistoricalDataReady(tokenDetails)
|
self.view.setTokenHistoricalDataReady(tokenDetails)
|
||||||
|
|
||||||
|
|
||||||
method fetchHistoricalBalanceForTokenAsJson*(self: Module, address: string, symbol: string, timeIntervalEnum: int) =
|
method fetchHistoricalBalanceForTokenAsJson*(self: Module, address: string, tokenSymbol: string, currencySymbol: string, timeIntervalEnum: int) =
|
||||||
self.controller.fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum)
|
self.controller.fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol,timeIntervalEnum)
|
||||||
|
|
||||||
method tokenBalanceHistoryDataResolved*(self: Module, balanceHistoryJson: string) =
|
method tokenBalanceHistoryDataResolved*(self: Module, balanceHistoryJson: string) =
|
||||||
self.view.setTokenBalanceHistoryDataReady(balanceHistoryJson)
|
self.view.setTokenBalanceHistoryDataReady(balanceHistoryJson)
|
||||||
|
|
|
@ -65,9 +65,9 @@ QtObject:
|
||||||
self.setMarketHistoryIsLoading(false)
|
self.setMarketHistoryIsLoading(false)
|
||||||
self.tokenHistoricalDataReady(tokenDetails)
|
self.tokenHistoricalDataReady(tokenDetails)
|
||||||
|
|
||||||
proc fetchHistoricalBalanceForTokenAsJson*(self: View, address: string, symbol: string, timeIntervalEnum: int) {.slot.} =
|
proc fetchHistoricalBalanceForTokenAsJson*(self: View, address: string, tokenSymbol: string, currencySymbol: string, timeIntervalEnum: int) {.slot.} =
|
||||||
self.setBalanceHistoryIsLoading(true)
|
self.setBalanceHistoryIsLoading(true)
|
||||||
self.delegate.fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum)
|
self.delegate.fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol, timeIntervalEnum)
|
||||||
|
|
||||||
proc tokenBalanceHistoryDataReady*(self: View, balanceHistoryJson: string) {.signal.}
|
proc tokenBalanceHistoryDataReady*(self: View, balanceHistoryJson: string) {.signal.}
|
||||||
|
|
||||||
|
|
|
@ -66,9 +66,10 @@ type
|
||||||
|
|
||||||
type
|
type
|
||||||
GetTokenBalanceHistoryDataTaskArg = ref object of QObjectTaskArg
|
GetTokenBalanceHistoryDataTaskArg = ref object of QObjectTaskArg
|
||||||
chainId: int
|
chainIds: seq[int]
|
||||||
address: string
|
address: string
|
||||||
symbol: string
|
tokenSymbol: string
|
||||||
|
currencySymbol: string
|
||||||
timeInterval: BalanceHistoryTimeInterval
|
timeInterval: BalanceHistoryTimeInterval
|
||||||
|
|
||||||
const getTokenBalanceHistoryDataTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
const getTokenBalanceHistoryDataTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
|
||||||
|
@ -76,12 +77,13 @@ const getTokenBalanceHistoryDataTask*: Task = proc(argEncoded: string) {.gcsafe,
|
||||||
var response = %*{}
|
var response = %*{}
|
||||||
try:
|
try:
|
||||||
# status-go time intervals are starting from 1
|
# status-go time intervals are starting from 1
|
||||||
response = backend.getBalanceHistory(arg.chainId, arg.address, int(arg.timeInterval) + 1).result
|
response = backend.getBalanceHistory(arg.chainIds, arg.address, arg.tokenSymbol, arg.currencySymbol, int(arg.timeInterval) + 1).result
|
||||||
|
|
||||||
let output = %* {
|
let output = %* {
|
||||||
"chainId": arg.chainId,
|
"chainIds": arg.chainIds,
|
||||||
"address": arg.address,
|
"address": arg.address,
|
||||||
"symbol": arg.symbol,
|
"tokenSymbol": arg.tokenSymbol,
|
||||||
|
"currencySymbol": arg.currencySymbol,
|
||||||
"timeInterval": int(arg.timeInterval),
|
"timeInterval": int(arg.timeInterval),
|
||||||
"historicalData": response
|
"historicalData": response
|
||||||
}
|
}
|
||||||
|
@ -90,10 +92,11 @@ const getTokenBalanceHistoryDataTask*: Task = proc(argEncoded: string) {.gcsafe,
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
let output = %* {
|
let output = %* {
|
||||||
"chainId": arg.chainId,
|
"chainIds": arg.chainIds,
|
||||||
"address": arg.address,
|
"address": arg.address,
|
||||||
"symbol": arg.symbol,
|
"tokenSymbol": arg.tokenSymbol,
|
||||||
|
"currencySymbol": arg.currencySymbol,
|
||||||
"timeInterval": int(arg.timeInterval),
|
"timeInterval": int(arg.timeInterval),
|
||||||
"error": "Balance history value not found",
|
"error": e.msg,
|
||||||
}
|
}
|
||||||
arg.finish(output)
|
arg.finish(output)
|
|
@ -242,31 +242,43 @@ QtObject:
|
||||||
)
|
)
|
||||||
self.threadpool.start(arg)
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
# Historical Balance
|
# Callback to process the response of fetchHistoricalBalanceForTokenAsJson call
|
||||||
proc tokenBalanceHistoryDataResolved*(self: Service, response: string) {.slot.} =
|
proc tokenBalanceHistoryDataResolved*(self: Service, response: string) {.slot.} =
|
||||||
# TODO
|
|
||||||
let responseObj = response.parseJson
|
let responseObj = response.parseJson
|
||||||
if (responseObj.kind != JObject):
|
if (responseObj.kind != JObject):
|
||||||
info "blance history response is not a json object"
|
warn "blance history response is not a json object"
|
||||||
return
|
return
|
||||||
|
|
||||||
self.events.emit(SIGNAL_BALANCE_HISTORY_DATA_READY, TokenBalanceHistoryDataArgs(
|
self.events.emit(SIGNAL_BALANCE_HISTORY_DATA_READY, TokenBalanceHistoryDataArgs(
|
||||||
result: response
|
result: response
|
||||||
))
|
))
|
||||||
|
|
||||||
proc fetchHistoricalBalanceForTokenAsJson*(self: Service, address: string, symbol: string, timeInterval: BalanceHistoryTimeInterval) =
|
proc fetchHistoricalBalanceForTokenAsJson*(self: Service, address: string, tokenSymbol: string, currencySymbol: string, timeInterval: BalanceHistoryTimeInterval) =
|
||||||
|
# create an empty list of chain ids
|
||||||
|
var chainIds: seq[int] = @[]
|
||||||
let networks = self.networkService.getNetworks()
|
let networks = self.networkService.getNetworks()
|
||||||
for network in networks:
|
for network in networks:
|
||||||
if network.enabled and network.nativeCurrencySymbol == symbol:
|
if network.enabled:
|
||||||
|
if network.nativeCurrencySymbol == tokenSymbol:
|
||||||
|
chainIds.add(network.chainId)
|
||||||
|
else:
|
||||||
|
for token in self.tokens[network.chainId]:
|
||||||
|
if token.symbol == tokenSymbol:
|
||||||
|
chainIds.add(network.chainId)
|
||||||
|
|
||||||
|
if chainIds.len == 0:
|
||||||
|
error "faild to find a network with the symbol", tokenSymbol
|
||||||
|
return
|
||||||
|
|
||||||
let arg = GetTokenBalanceHistoryDataTaskArg(
|
let arg = GetTokenBalanceHistoryDataTaskArg(
|
||||||
tptr: cast[ByteAddress](getTokenBalanceHistoryDataTask),
|
tptr: cast[ByteAddress](getTokenBalanceHistoryDataTask),
|
||||||
vptr: cast[ByteAddress](self.vptr),
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
slot: "tokenBalanceHistoryDataResolved",
|
slot: "tokenBalanceHistoryDataResolved",
|
||||||
chainId: network.chainId,
|
chainIds: chainIds,
|
||||||
address: address,
|
address: address,
|
||||||
symbol: symbol,
|
tokenSymbol: tokenSymbol,
|
||||||
|
currencySymbol: currencySymbol,
|
||||||
timeInterval: timeInterval
|
timeInterval: timeInterval
|
||||||
)
|
)
|
||||||
self.threadpool.start(arg)
|
self.threadpool.start(arg)
|
||||||
return
|
return
|
||||||
error "faild to find a network with the symbol", symbol
|
|
|
@ -75,8 +75,6 @@ type AccountDeleted* = ref object of Args
|
||||||
|
|
||||||
type CurrencyUpdated = ref object of Args
|
type CurrencyUpdated = ref object of Args
|
||||||
|
|
||||||
type TokenVisibilityToggled = ref object of Args
|
|
||||||
|
|
||||||
type NetwordkEnabledToggled = ref object of Args
|
type NetwordkEnabledToggled = ref object of Args
|
||||||
|
|
||||||
type WalletAccountUpdated* = ref object of Args
|
type WalletAccountUpdated* = ref object of Args
|
||||||
|
@ -523,6 +521,9 @@ QtObject:
|
||||||
|
|
||||||
proc onAllTokensBuilt*(self: Service, response: string) {.slot.} =
|
proc onAllTokensBuilt*(self: Service, response: string) {.slot.} =
|
||||||
try:
|
try:
|
||||||
|
var visibleSymbols: seq[string]
|
||||||
|
let chainIds = self.networkService.getNetworks().map(n => n.chainId)
|
||||||
|
|
||||||
let responseObj = response.parseJson
|
let responseObj = response.parseJson
|
||||||
var data = TokensPerAccountArgs()
|
var data = TokensPerAccountArgs()
|
||||||
if responseObj.kind == JObject:
|
if responseObj.kind == JObject:
|
||||||
|
@ -534,7 +535,13 @@ QtObject:
|
||||||
data.accountsTokens[wAddress] = tokens
|
data.accountsTokens[wAddress] = tokens
|
||||||
self.storeTokensForAccount(wAddress, tokens)
|
self.storeTokensForAccount(wAddress, tokens)
|
||||||
self.tokenService.updateTokenPrices(tokens) # For efficiency. Will be removed when token info fetching gets moved to the tokenService
|
self.tokenService.updateTokenPrices(tokens) # For efficiency. Will be removed when token info fetching gets moved to the tokenService
|
||||||
|
# Gather symbol for visible tokens
|
||||||
|
for token in tokens:
|
||||||
|
if token.getVisibleForNetworkWithPositiveBalance(chainIds) and find(visibleSymbols, token.symbol) == -1:
|
||||||
|
visibleSymbols.add(token.symbol)
|
||||||
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, data)
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, data)
|
||||||
|
|
||||||
|
discard backend.updateVisibleTokens(visibleSymbols)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error "error: ", procName="onAllTokensBuilt", errName = e.name, errDesription = e.msg
|
error "error: ", procName="onAllTokensBuilt", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,9 @@ rpc(getWalletToken, "wallet"):
|
||||||
rpc(startWallet, "wallet"):
|
rpc(startWallet, "wallet"):
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
rpc(updateVisibleTokens, "wallet"):
|
||||||
|
symbols: seq[string]
|
||||||
|
|
||||||
rpc(getTransactionEstimatedTime, "wallet"):
|
rpc(getTransactionEstimatedTime, "wallet"):
|
||||||
chainId: int
|
chainId: int
|
||||||
maxFeePerGas: float
|
maxFeePerGas: float
|
||||||
|
@ -288,8 +291,10 @@ rpc(getName, "ens"):
|
||||||
address: string
|
address: string
|
||||||
|
|
||||||
rpc(getBalanceHistory, "wallet"):
|
rpc(getBalanceHistory, "wallet"):
|
||||||
chainId: int
|
chainIds: seq[int]
|
||||||
address: string
|
address: string
|
||||||
|
tokenSymbol: string
|
||||||
|
currencySymbol: string
|
||||||
timeInterval: int
|
timeInterval: int
|
||||||
|
|
||||||
rpc(isCurrencyFiat, "wallet"):
|
rpc(isCurrencyFiat, "wallet"):
|
||||||
|
|
|
@ -207,7 +207,7 @@ TEST(WalletApi, TestGetTokens)
|
||||||
auto networks = Wallet::getEthereumChains(false);
|
auto networks = Wallet::getEthereumChains(false);
|
||||||
ASSERT_GT(networks.size(), 0);
|
ASSERT_GT(networks.size(), 0);
|
||||||
auto mainNetIt =
|
auto mainNetIt =
|
||||||
std::find_if(networks.begin(), networks.end(), [](const auto& n) { return n.chainName == "Mainnet"; });
|
std::find_if(networks.begin(), networks.end(), [](const auto& n) { return n.chainName == "Ethereum Mainnet"; });
|
||||||
ASSERT_NE(mainNetIt, networks.end());
|
ASSERT_NE(mainNetIt, networks.end());
|
||||||
const auto& mainNet = *mainNetIt;
|
const auto& mainNet = *mainNetIt;
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ TEST(WalletApi, TestGetTokensBalancesForChainIDs)
|
||||||
ASSERT_GT(networks.size(), 1);
|
ASSERT_GT(networks.size(), 1);
|
||||||
|
|
||||||
auto mainNetIt =
|
auto mainNetIt =
|
||||||
std::find_if(networks.begin(), networks.end(), [](const auto& n) { return n.chainName == "Mainnet"; });
|
std::find_if(networks.begin(), networks.end(), [](const auto& n) { return n.chainName == "Ethereum Mainnet"; });
|
||||||
ASSERT_NE(mainNetIt, networks.end());
|
ASSERT_NE(mainNetIt, networks.end());
|
||||||
const auto& mainNet = *mainNetIt;
|
const auto& mainNet = *mainNetIt;
|
||||||
|
|
||||||
|
@ -265,21 +265,68 @@ TEST(WalletApi, TestGetTokensBalancesForChainIDs)
|
||||||
ASSERT_EQ(toQString(addressBalance.at(sntTest.address)), "0");
|
ASSERT_EQ(toQString(addressBalance.at(sntTest.address)), "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TestNetwork
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
bool isTest;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestParams
|
||||||
|
{
|
||||||
|
Accounts::EOAddress walletAddress;
|
||||||
|
std::vector<TestNetwork> networks;
|
||||||
|
QString token;
|
||||||
|
QString newTestAccountName;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::vector<TestParams> allTestParams{
|
||||||
|
// [0] - Goerli ERC20 token test account set
|
||||||
|
TestParams{Accounts::EOAddress("0x586e3bd2c40b0f243162ea563a1f43ae9ef25ef9"),
|
||||||
|
{{QString("Goerli"), true}},
|
||||||
|
QString("USDC"),
|
||||||
|
u"test_watch_only-name"_qs},
|
||||||
|
// [1] - Main net ERC20 token test account set
|
||||||
|
TestParams{Accounts::EOAddress("0xae0c364acb9b105766fea91cfa5aaea31a1821c1"),
|
||||||
|
{{QString("Ethereum Mainnet"), false}},
|
||||||
|
QString("SNT"),
|
||||||
|
u"test_watch_only-name"_qs},
|
||||||
|
// [2] - Main net native token account set
|
||||||
|
TestParams{Accounts::EOAddress("0x0182e671dfd5f7d21b13714cbe9f92d26b59eeb9"),
|
||||||
|
{{QString("Ethereum Mainnet"), false}},
|
||||||
|
QString("USDC"),
|
||||||
|
u"test_watch_only-name"_qs},
|
||||||
|
// [3] - Main net ERC20 test account set
|
||||||
|
TestParams{Accounts::EOAddress("0x473780deAF4a2Ac070BBbA936B0cdefe7F267dFc"),
|
||||||
|
{{QString("Ethereum Mainnet"), false}},
|
||||||
|
QString("ETH"),
|
||||||
|
u"test_watch_only-name"_qs},
|
||||||
|
// [4] - Arbitrum Goerli native token test account set
|
||||||
|
TestParams{Accounts::EOAddress("0xE2d622C817878dA5143bBE06866ca8E35273Ba8a"),
|
||||||
|
{{QString("Arbitrum Goerli"), true}, {QString("Goerli"), true}},
|
||||||
|
QString("ETH"),
|
||||||
|
u"test_watch_only-name"_qs},
|
||||||
|
};
|
||||||
|
|
||||||
|
TestParams getTestParams()
|
||||||
|
{
|
||||||
|
return allTestParams[4];
|
||||||
|
}
|
||||||
|
|
||||||
TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount)
|
TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount)
|
||||||
{
|
{
|
||||||
ScopedTestAccount testAccount(test_info_->name());
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
const auto newTestAccountName = u"test_watch_only-name"_qs;
|
const auto params = getTestParams();
|
||||||
|
|
||||||
Accounts::addAccountWatch(Accounts::EOAddress("0xdb5ac1a559b02e12f29fc0ec0e37be8e046def49"),
|
Accounts::addAccountWatch(Accounts::EOAddress("0xdb5ac1a559b02e12f29fc0ec0e37be8e046def49"),
|
||||||
newTestAccountName,
|
params.newTestAccountName,
|
||||||
QColor("fuchsia"),
|
QColor("fuchsia"),
|
||||||
u""_qs);
|
u""_qs);
|
||||||
const auto updatedAccounts = Accounts::getAccounts();
|
const auto updatedAccounts = Accounts::getAccounts();
|
||||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||||
|
|
||||||
const auto newAccountIt =
|
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [¶ms](const auto& a) {
|
||||||
std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [&newTestAccountName](const auto& a) {
|
return a.name == params.newTestAccountName;
|
||||||
return a.name == newTestAccountName;
|
|
||||||
});
|
});
|
||||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||||
const auto& newAccount = *newAccountIt;
|
const auto& newAccount = *newAccountIt;
|
||||||
|
@ -288,7 +335,7 @@ TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount)
|
||||||
ASSERT_GT(networks.size(), 1);
|
ASSERT_GT(networks.size(), 1);
|
||||||
|
|
||||||
auto mainNetIt =
|
auto mainNetIt =
|
||||||
std::find_if(networks.begin(), networks.end(), [](const auto& n) { return n.chainName == "Mainnet"; });
|
std::find_if(networks.begin(), networks.end(), [](const auto& n) { return n.chainName == "Ethereum Mainnet"; });
|
||||||
ASSERT_NE(mainNetIt, networks.end());
|
ASSERT_NE(mainNetIt, networks.end());
|
||||||
const auto& mainNet = *mainNetIt;
|
const auto& mainNet = *mainNetIt;
|
||||||
|
|
||||||
|
@ -309,23 +356,23 @@ TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount)
|
||||||
ASSERT_GT(addressBalance.at(sntMain.address), 0);
|
ASSERT_GT(addressBalance.at(sntMain.address), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is a debugging test. Augment it with local Ganache environment to have a reliable test
|
// TODO: this is a debugging test. Augment it with local Ganache environment to have a reliable integration test
|
||||||
TEST(WalletApi, TestCheckRecentHistory)
|
TEST(WalletApi, TestCheckRecentHistory)
|
||||||
{
|
{
|
||||||
ScopedTestAccount testAccount(test_info_->name());
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
const auto params = getTestParams();
|
||||||
|
|
||||||
// Add watch account
|
// Add watch account
|
||||||
const auto newTestAccountName = u"test_watch_only-name"_qs;
|
|
||||||
Accounts::addAccountWatch(Accounts::EOAddress("0xe74E17D586227691Cb7b64ed78b1b7B14828B034"),
|
Accounts::addAccountWatch(Accounts::EOAddress("0xe74E17D586227691Cb7b64ed78b1b7B14828B034"),
|
||||||
newTestAccountName,
|
params.newTestAccountName,
|
||||||
QColor("fuchsia"),
|
QColor("fuchsia"),
|
||||||
u""_qs);
|
u""_qs);
|
||||||
const auto updatedAccounts = Accounts::getAccounts();
|
const auto updatedAccounts = Accounts::getAccounts();
|
||||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||||
|
|
||||||
const auto newAccountIt =
|
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [¶ms](const auto& a) {
|
||||||
std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [newTestAccountName](const auto& a) {
|
return a.name == params.newTestAccountName;
|
||||||
return a.name == newTestAccountName;
|
|
||||||
});
|
});
|
||||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||||
const auto& newAccount = *newAccountIt;
|
const auto& newAccount = *newAccountIt;
|
||||||
|
@ -352,32 +399,43 @@ TEST(WalletApi, TestCheckRecentHistory)
|
||||||
ASSERT_TRUE(historyReady);
|
ASSERT_TRUE(historyReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is a debugging test. Augment it with local Ganache environment to have a reliable test
|
// TODO: this is a debugging test. Augment it with local Ganache environment to have a reliable integration test
|
||||||
TEST(WalletApi, TestGetBalanceHistory)
|
TEST(WalletApi, TestGetBalanceHistory)
|
||||||
{
|
{
|
||||||
ScopedTestAccount testAccount(test_info_->name());
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
// Add watch account
|
const auto params = getTestParams();
|
||||||
const auto newTestAccountName = u"test_watch_only-name"_qs;
|
|
||||||
Accounts::addAccountWatch(Accounts::EOAddress("0x473780deAF4a2Ac070BBbA936B0cdefe7F267dFc"),
|
Accounts::addAccountWatch(params.walletAddress, params.newTestAccountName, QColor("fuchsia"), u""_qs);
|
||||||
newTestAccountName,
|
|
||||||
QColor("fuchsia"),
|
|
||||||
u""_qs);
|
|
||||||
const auto updatedAccounts = Accounts::getAccounts();
|
const auto updatedAccounts = Accounts::getAccounts();
|
||||||
ASSERT_EQ(updatedAccounts.size(), 3);
|
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||||
|
|
||||||
auto networks = Wallet::getEthereumChains(false);
|
auto networks = Wallet::getEthereumChains(false);
|
||||||
ASSERT_GT(networks.size(), 0);
|
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);
|
std::vector<Wallet::ChainID> chainIDs;
|
||||||
|
for(const auto& net : params.networks)
|
||||||
|
{
|
||||||
|
auto netIt = std::find_if(networks.begin(), networks.end(), [&net](const auto& n) {
|
||||||
|
return n.chainName == net.name && n.isTest == net.isTest;
|
||||||
|
});
|
||||||
|
ASSERT_NE(netIt, networks.end());
|
||||||
|
chainIDs.push_back(netIt->chainId);
|
||||||
|
|
||||||
const auto newAccountIt =
|
auto nativeIt = std::find_if(networks.begin(), networks.end(), [¶ms](const auto& n) {
|
||||||
std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [newTestAccountName](const auto& a) {
|
return n.nativeCurrencySymbol == params.token;
|
||||||
return a.name == newTestAccountName;
|
});
|
||||||
|
if(nativeIt == networks.end())
|
||||||
|
{
|
||||||
|
auto tokens = Wallet::getTokens(netIt->chainId);
|
||||||
|
auto tokenIt = std::find_if(
|
||||||
|
tokens.begin(), tokens.end(), [¶ms](const auto& t) { return t.symbol == params.token; });
|
||||||
|
ASSERT_NE(tokenIt, tokens.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [¶ms](const auto& a) {
|
||||||
|
return a.name == params.newTestAccountName;
|
||||||
});
|
});
|
||||||
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
ASSERT_NE(newAccountIt, updatedAccounts.end());
|
||||||
const auto& newAccount = *newAccountIt;
|
const auto& newAccount = *newAccountIt;
|
||||||
|
@ -395,11 +453,59 @@ TEST(WalletApi, TestGetBalanceHistory)
|
||||||
{Wallet::BalanceHistoryTimeInterval::BalanceHistory1Year, "1Y"},
|
{Wallet::BalanceHistoryTimeInterval::BalanceHistory1Year, "1Y"},
|
||||||
{Wallet::BalanceHistoryTimeInterval::BalanceHistoryAllTime, "All"}};
|
{Wallet::BalanceHistoryTimeInterval::BalanceHistoryAllTime, "All"}};
|
||||||
|
|
||||||
|
// Fetch first and watch for finished signal of newAccount.address
|
||||||
|
int bhStartedReceivedCount = 0;
|
||||||
|
int bhEndedReceivedCount = 0;
|
||||||
|
int bhErrorReceivedCount = 0;
|
||||||
|
bool targetAccountDone = false;
|
||||||
|
QObject::connect(
|
||||||
|
StatusGo::SignalsManager::instance(),
|
||||||
|
&StatusGo::SignalsManager::wallet,
|
||||||
|
testAccount.app(),
|
||||||
|
[&targetAccountDone, &bhStartedReceivedCount, &bhEndedReceivedCount, &bhErrorReceivedCount, &newAccount](
|
||||||
|
QSharedPointer<StatusGo::EventData> data) {
|
||||||
|
Wallet::Transfer::Event event = data->eventInfo();
|
||||||
|
if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateStarted)
|
||||||
|
{
|
||||||
|
bhStartedReceivedCount++;
|
||||||
|
}
|
||||||
|
else if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateFinished)
|
||||||
|
{
|
||||||
|
bhEndedReceivedCount++;
|
||||||
|
if(event.accounts)
|
||||||
|
{
|
||||||
|
auto found = std::find_if(event.accounts.value().begin(),
|
||||||
|
event.accounts.value().end(),
|
||||||
|
[&event, &newAccount](const auto& a) { return a == newAccount.address; });
|
||||||
|
if(found != event.accounts.value().end())
|
||||||
|
{
|
||||||
|
targetAccountDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateFinishedWithError)
|
||||||
|
{
|
||||||
|
bhErrorReceivedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<QString> symbols{params.token};
|
||||||
|
Wallet::updateVisibleTokens(symbols);
|
||||||
|
|
||||||
|
Wallet::startWallet();
|
||||||
|
|
||||||
|
testAccount.processMessages(2400000, [&targetAccountDone, &bhEndedReceivedCount, &bhErrorReceivedCount]() {
|
||||||
|
return !targetAccountDone && bhEndedReceivedCount < 2 && bhErrorReceivedCount < 1;
|
||||||
|
});
|
||||||
|
ASSERT_GT(bhStartedReceivedCount, 0);
|
||||||
|
ASSERT_EQ(bhErrorReceivedCount, 0);
|
||||||
|
ASSERT_GT(bhEndedReceivedCount, 0);
|
||||||
|
ASSERT_TRUE(targetAccountDone);
|
||||||
|
|
||||||
for(const auto& historyInterval : testIntervals)
|
for(const auto& historyInterval : testIntervals)
|
||||||
{
|
{
|
||||||
// TODO: next `mainNet.nativeCurrencySymbol`, later `tokens.symbol`
|
auto balanceHistory = Wallet::getBalanceHistory(chainIDs, newAccount.address, params.token, historyInterval);
|
||||||
auto balanceHistory = Wallet::getBalanceHistory(mainNet.chainId, newAccount.address, historyInterval);
|
ASSERT_GT(balanceHistory.size(), 0);
|
||||||
ASSERT_TRUE(balanceHistory.size() > 0);
|
|
||||||
|
|
||||||
auto weiToEth = [](const StatusGo::Wallet::BigInt& wei) -> double {
|
auto weiToEth = [](const StatusGo::Wallet::BigInt& wei) -> double {
|
||||||
StatusGo::Wallet::BigInt q; // wei / eth
|
StatusGo::Wallet::BigInt q; // wei / eth
|
||||||
|
@ -413,19 +519,145 @@ TEST(WalletApi, TestGetBalanceHistory)
|
||||||
return q.convert_to<double>() + (qSzabos.convert_to<double>() / ((weiD / szaboD).convert_to<double>()));
|
return q.convert_to<double>() + (qSzabos.convert_to<double>() / ((weiD / szaboD).convert_to<double>()));
|
||||||
};
|
};
|
||||||
|
|
||||||
QFile file(QString("/tmp/balance_history-%s.csv").arg(testIntervalsStrs[historyInterval]));
|
auto fileInfo = QFileInfo(
|
||||||
|
QString("/tmp/StatusTests/balance/balance_history-%1.csv").arg(testIntervalsStrs[historyInterval]));
|
||||||
|
QFile file(fileInfo.absoluteFilePath());
|
||||||
|
QDir().mkpath(fileInfo.absolutePath());
|
||||||
if(file.open(QIODevice::WriteOnly | QIODevice::Text))
|
if(file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
{
|
{
|
||||||
QTextStream out(&file);
|
QTextStream out(&file);
|
||||||
out << "Balance, Timestamp" << Qt::endl;
|
out << "Balance, Timestamp, Block Number, Formatted Date" << Qt::endl;
|
||||||
for(int i = 0; i < balanceHistory.size(); ++i)
|
for(int i = 0; i < balanceHistory.size(); ++i)
|
||||||
{
|
{
|
||||||
out << weiToEth(balanceHistory[i].value) << "," << balanceHistory[i].time.toSecsSinceEpoch()
|
out << weiToEth(balanceHistory[i].value) << "," << balanceHistory[i].time.toSecsSinceEpoch() << ","
|
||||||
<< Qt::endl;
|
<< balanceHistory[i].blockNumber.str().c_str() << ","
|
||||||
}
|
<< balanceHistory[i].time.toString("dd.MM.yyyy hh:mm:ss") << Qt::endl;
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is a debugging test. Augment it with local Ganache environment to have a reliable integration test
|
||||||
|
TEST(WalletApi, TestStartWallet)
|
||||||
|
{
|
||||||
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
auto params = getTestParams();
|
||||||
|
|
||||||
|
// Add watch account
|
||||||
|
Accounts::addAccountWatch(Accounts::EOAddress("0xF38D2CD3C6Ad02dD6f8B68E0A7b2f959819954b6"),
|
||||||
|
params.newTestAccountName,
|
||||||
|
QColor("fuchsia"),
|
||||||
|
u""_qs);
|
||||||
|
const auto updatedAccounts = Accounts::getAccounts();
|
||||||
|
ASSERT_EQ(updatedAccounts.size(), 3);
|
||||||
|
|
||||||
|
int bhStartedReceivedCount = 0;
|
||||||
|
int bhEndedReceivedCount = 0;
|
||||||
|
int bhErrorReceivedCount = 0;
|
||||||
|
QObject::connect(StatusGo::SignalsManager::instance(),
|
||||||
|
&StatusGo::SignalsManager::wallet,
|
||||||
|
testAccount.app(),
|
||||||
|
[&bhStartedReceivedCount, &bhEndedReceivedCount, &bhErrorReceivedCount](
|
||||||
|
QSharedPointer<StatusGo::EventData> data) {
|
||||||
|
Wallet::Transfer::Event event = data->eventInfo();
|
||||||
|
if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateStarted)
|
||||||
|
{
|
||||||
|
bhStartedReceivedCount++;
|
||||||
|
}
|
||||||
|
else if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateFinished)
|
||||||
|
{
|
||||||
|
bhEndedReceivedCount++;
|
||||||
|
}
|
||||||
|
else if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateFinishedWithError)
|
||||||
|
{
|
||||||
|
bhErrorReceivedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<QString> symbols{params.token};
|
||||||
|
Wallet::updateVisibleTokens(symbols);
|
||||||
|
|
||||||
|
Wallet::startWallet();
|
||||||
|
|
||||||
|
testAccount.processMessages(240000, [&bhEndedReceivedCount, &bhErrorReceivedCount]() {
|
||||||
|
return bhEndedReceivedCount < 2 && bhErrorReceivedCount < 1;
|
||||||
|
});
|
||||||
|
ASSERT_EQ(bhStartedReceivedCount, 2);
|
||||||
|
ASSERT_EQ(bhErrorReceivedCount, 0);
|
||||||
|
ASSERT_EQ(bhEndedReceivedCount, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is a debugging test. Augment it with local Ganache environment to have a reliable integration test
|
||||||
|
TEST(WalletApi, TestStopBalanceHistory)
|
||||||
|
{
|
||||||
|
ScopedTestAccount testAccount(test_info_->name());
|
||||||
|
|
||||||
|
const auto updatedAccounts = Accounts::getAccounts();
|
||||||
|
ASSERT_EQ(updatedAccounts.size(), 2);
|
||||||
|
|
||||||
|
int bhStartedReceivedCount = 0;
|
||||||
|
int bhEndedReceivedCount = 0;
|
||||||
|
int bhErrorReceivedCount = 0;
|
||||||
|
QString stopMessage;
|
||||||
|
QObject::connect(StatusGo::SignalsManager::instance(),
|
||||||
|
&StatusGo::SignalsManager::wallet,
|
||||||
|
testAccount.app(),
|
||||||
|
[&stopMessage, &bhStartedReceivedCount, &bhEndedReceivedCount, &bhErrorReceivedCount](
|
||||||
|
QSharedPointer<StatusGo::EventData> data) {
|
||||||
|
Wallet::Transfer::Event event = data->eventInfo();
|
||||||
|
if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateStarted)
|
||||||
|
{
|
||||||
|
bhStartedReceivedCount++;
|
||||||
|
}
|
||||||
|
else if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateFinished)
|
||||||
|
{
|
||||||
|
stopMessage = event.message;
|
||||||
|
bhEndedReceivedCount++;
|
||||||
|
}
|
||||||
|
else if(event.type == Wallet::Transfer::Events::EventBalanceHistoryUpdateFinishedWithError)
|
||||||
|
{
|
||||||
|
bhErrorReceivedCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Wallet::updateVisibleTokens({QString("ETH")});
|
||||||
|
|
||||||
|
Wallet::startWallet();
|
||||||
|
|
||||||
|
bool stopCalled = false;
|
||||||
|
testAccount.processMessages(5000,
|
||||||
|
[&stopCalled, &bhStartedReceivedCount, &bhEndedReceivedCount, &bhErrorReceivedCount]() {
|
||||||
|
if(bhStartedReceivedCount == 1 && !stopCalled)
|
||||||
|
{
|
||||||
|
stopCalled = true;
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
Wallet::stopWallet();
|
||||||
|
}
|
||||||
|
return bhEndedReceivedCount < 1 && bhErrorReceivedCount < 1;
|
||||||
|
});
|
||||||
|
ASSERT_EQ(bhStartedReceivedCount, 1);
|
||||||
|
ASSERT_EQ(bhErrorReceivedCount, 0);
|
||||||
|
ASSERT_EQ(bhEndedReceivedCount, 1);
|
||||||
|
ASSERT_EQ("Service canceled", stopMessage);
|
||||||
|
|
||||||
|
stopMessage = "";
|
||||||
|
|
||||||
|
// Do an empty run
|
||||||
|
Wallet::updateVisibleTokens({});
|
||||||
|
|
||||||
|
Wallet::stopWallet();
|
||||||
|
|
||||||
|
stopCalled = false;
|
||||||
|
testAccount.processMessages(1000,
|
||||||
|
[&stopCalled, &bhStartedReceivedCount, &bhEndedReceivedCount, &bhErrorReceivedCount]() {
|
||||||
|
return bhEndedReceivedCount < 2 && bhErrorReceivedCount < 2;
|
||||||
|
});
|
||||||
|
|
||||||
|
ASSERT_EQ(bhStartedReceivedCount, 2);
|
||||||
|
ASSERT_EQ(bhErrorReceivedCount, 0);
|
||||||
|
ASSERT_EQ(bhEndedReceivedCount, 2);
|
||||||
|
ASSERT_EQ("", stopMessage);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Status::Testing
|
} // namespace Status::Testing
|
||||||
|
|
|
@ -10,7 +10,9 @@ QtObject {
|
||||||
if (Number.isInteger(num))
|
if (Number.isInteger(num))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return num.toString().split('.')[1].length
|
let parts = num.toString().split('.')
|
||||||
|
// Decimal trick doesn't work for numbers represented in scientific notation, hence the hardcoded fallback
|
||||||
|
return (parts.length > 1 && parts[1].indexOf("e") == -1) ? parts[1].length : 2
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripTrailingZeroes(numStr, locale) {
|
function stripTrailingZeroes(numStr, locale) {
|
||||||
|
|
|
@ -20,18 +20,12 @@ import shared.stores 1.0
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var token
|
property var token: {}
|
||||||
/*required*/ property string address: ""
|
/*required*/ property string address: ""
|
||||||
|
|
||||||
function createStore(address) {
|
|
||||||
return balanceHistoryComponent.createObject(null, {address: address})
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
property var marketValueStore : RootStore.marketValueStore
|
property var marketValueStore : RootStore.marketValueStore
|
||||||
// TODO: Should be temporary until non native tokens are supported by balance history
|
|
||||||
property bool isNativeToken: typeof token !== "undefined" && token ? token.symbol === "ETH" : false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
@ -115,11 +109,7 @@ Item {
|
||||||
|
|
||||||
graphsModel: [
|
graphsModel: [
|
||||||
{text: qsTr("Price"), enabled: true, id: AssetsDetailView.GraphType.Price},
|
{text: qsTr("Price"), enabled: true, id: AssetsDetailView.GraphType.Price},
|
||||||
{
|
{text: qsTr("Balance"), enabled: true, id: AssetsDetailView.GraphType.Balance},
|
||||||
text: qsTr("Balance"),
|
|
||||||
enabled: false, // TODO: Enable after adding ECR20 token support and DB cache. Current prototype implementation works only for d.isNativeToken
|
|
||||||
id: AssetsDetailView.GraphType.Balance
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
defaultTimeRangeIndexShown: ChartStoreBase.TimeRange.All
|
defaultTimeRangeIndexShown: ChartStoreBase.TimeRange.All
|
||||||
timeRangeModel: dataReady() && selectedStore.timeRangeTabsModel
|
timeRangeModel: dataReady() && selectedStore.timeRangeTabsModel
|
||||||
|
@ -129,11 +119,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance) {
|
if(graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance) {
|
||||||
let selectedTimeRangeEnum = balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)
|
graphDetail.updateBalanceStore()
|
||||||
if(balanceStore.isTimeToRequest(selectedTimeRangeEnum)) {
|
|
||||||
RootStore.fetchHistoricalBalanceForTokenAsJson(root.address, token.symbol, selectedTimeRangeEnum)
|
|
||||||
balanceStore.updateRequestTime(selectedTimeRangeEnum)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isTimeRange) {
|
if(!isTimeRange) {
|
||||||
|
@ -241,17 +227,33 @@ Item {
|
||||||
active: RootStore.marketHistoryIsLoading
|
active: RootStore.marketHistoryIsLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateBalanceStore() {
|
||||||
|
let selectedTimeRangeEnum = balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)
|
||||||
|
|
||||||
|
let currencySymbol = RootStore.currencyStore.currentCurrency
|
||||||
|
if(!balanceStore.hasData(root.address, token.symbol, currencySymbol, selectedTimeRangeEnum)) {
|
||||||
|
RootStore.fetchHistoricalBalanceForTokenAsJson(root.address, token.symbol, currencySymbol, selectedTimeRangeEnum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TokenBalanceHistoryStore {
|
TokenBalanceHistoryStore {
|
||||||
id: balanceStore
|
id: balanceStore
|
||||||
|
|
||||||
address: root.address
|
onNewDataReady: (address, tokenSymbol, currencySymbol, timeRange) => {
|
||||||
|
if (timeRange === timeRangeStrToEnum(graphDetail.selectedTimeRange)) {
|
||||||
onNewDataReady: (timeRange) => {
|
|
||||||
let selectedTimeRange = timeRangeStrToEnum(graphDetail.selectedTimeRange)
|
|
||||||
if (timeRange === selectedTimeRange && address === root.address) {
|
|
||||||
chart.updateToNewData()
|
chart.updateToNewData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onAddressChanged() { graphDetail.updateBalanceStore() }
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: token
|
||||||
|
function onSymbolChanged() { graphDetail.updateBalanceStore() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var timeRangeTabsModel: [
|
readonly property var timeRangeTabsModel: [
|
||||||
{text: qsTr("7D"), enabled: true, timeRange: ChartStoreBase.TimeRange.Weekly},
|
{text: qsTr("7D"), enabled: true, timeRange: ChartStoreBase.TimeRange.Weekly, timeIndex: 0},
|
||||||
{text: qsTr("1M"), enabled: true, timeRange: ChartStoreBase.TimeRange.Monthly},
|
{text: qsTr("1M"), enabled: true, timeRange: ChartStoreBase.TimeRange.Monthly, timeIndex: 1},
|
||||||
{text: qsTr("6M"), enabled: true, timeRange: ChartStoreBase.TimeRange.HalfYearly},
|
{text: qsTr("6M"), enabled: true, timeRange: ChartStoreBase.TimeRange.HalfYearly, timeIndex: 2},
|
||||||
{text: qsTr("1Y"), enabled: true, timeRange: ChartStoreBase.TimeRange.Yearly},
|
{text: qsTr("1Y"), enabled: true, timeRange: ChartStoreBase.TimeRange.Yearly, timeIndex: 3},
|
||||||
{text: qsTr("ALL"), enabled: true, timeRange: ChartStoreBase.TimeRange.All}]
|
{text: qsTr("ALL"), enabled: true, timeRange: ChartStoreBase.TimeRange.All, timeIndex: 4}]
|
||||||
|
|
||||||
property var weeklyData: []
|
property var weeklyData: []
|
||||||
property var monthlyData: []
|
property var monthlyData: []
|
||||||
|
@ -66,61 +66,39 @@ Item {
|
||||||
]
|
]
|
||||||
|
|
||||||
/// \timeRange is the time range of the data that was updated
|
/// \timeRange is the time range of the data that was updated
|
||||||
signal newDataReady(int timeRange)
|
signal newDataReady(string address, string tokenSymbol, string currencySymbol, int timeRange)
|
||||||
|
|
||||||
function timeRangeEnumToStr(enumVal) {
|
function timeRangeEnumToStr(enumVal) {
|
||||||
return d.timeRangeTabsModel.get(enumVal)
|
return d.timeRangeEnumToPropertiesMap.get(enumVal).text
|
||||||
|
}
|
||||||
|
function timeRangeEnumToTimeIndex(enumVal) {
|
||||||
|
return d.timeRangeEnumToPropertiesMap.get(enumVal).timeIndex
|
||||||
}
|
}
|
||||||
function timeRangeStrToEnum(str) {
|
function timeRangeStrToEnum(str) {
|
||||||
return d.timeRangeStrToEnumMap.get(str)
|
return d.timeRangeStrToEnumMap.get(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \arg timeRange: of type ChartStoreBase.TimeRange
|
|
||||||
function updateRequestTime(timeRange) {
|
|
||||||
d.requestTimes.set(timeRange, new Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetRequestTime() {
|
|
||||||
d.requestTimes.set(timeRange, new Date(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \arg timeRange: of type ChartStoreBase.TimeRange
|
|
||||||
function isTimeToRequest(timeRange) {
|
|
||||||
if(d.requestTimes.has(timeRange)) {
|
|
||||||
const hoursToIgnore = 12
|
|
||||||
let existing = d.requestTimes.get(timeRange)
|
|
||||||
let willBeMs = new Date(existing.getTime() + (hoursToIgnore * 3600000))
|
|
||||||
return new Date(willBeMs) < new Date()
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
readonly property int hoursInADay: 24
|
readonly property int hoursInADay: 24
|
||||||
readonly property int avgLengthOfMonth: 30
|
readonly property int avgLengthOfMonth: 30
|
||||||
property var timeRangeEnumToStrMap: null
|
property var timeRangeEnumToPropertiesMap: null
|
||||||
property var timeRangeStrToEnumMap: null
|
property var timeRangeStrToEnumMap: null
|
||||||
property var requestTimes: null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if(d.timeRangeEnumToStrMap === null) {
|
if(d.timeRangeEnumToPropertiesMap === null) {
|
||||||
d.timeRangeEnumToStrMap = new Map()
|
d.timeRangeEnumToPropertiesMap = new Map()
|
||||||
for (const x of timeRangeTabsModel) {
|
for (const x of timeRangeTabsModel) {
|
||||||
d.timeRangeEnumToStrMap.set(x.timeRange, x.text)
|
d.timeRangeEnumToPropertiesMap.set(x.timeRange, x)
|
||||||
}
|
}
|
||||||
d.timeRangeStrToEnumMap = new Map()
|
d.timeRangeStrToEnumMap = new Map()
|
||||||
for (const x of d.timeRangeEnumToStrMap.entries()) {
|
for (const x of d.timeRangeEnumToPropertiesMap.entries()) {
|
||||||
let key = x[0]
|
let key = x[0]
|
||||||
let val = x[1]
|
let val = x[1]
|
||||||
d.timeRangeStrToEnumMap.set(val, key)
|
d.timeRangeStrToEnumMap.set(val.text, key)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if(d.requestTimes === null) {
|
|
||||||
d.requestTimes = new Map()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -229,8 +229,8 @@ QtObject {
|
||||||
property bool marketHistoryIsLoading: walletSectionAllTokens.marketHistoryIsLoading
|
property bool marketHistoryIsLoading: walletSectionAllTokens.marketHistoryIsLoading
|
||||||
|
|
||||||
// TODO: range until we optimize to cache the data and abuse the requests
|
// TODO: range until we optimize to cache the data and abuse the requests
|
||||||
function fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum) {
|
function fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol, timeIntervalEnum) {
|
||||||
walletSectionAllTokens.fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum)
|
walletSectionAllTokens.fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol, timeIntervalEnum)
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool balanceHistoryIsLoading: walletSectionAllTokens.balanceHistoryIsLoading
|
property bool balanceHistoryIsLoading: walletSectionAllTokens.balanceHistoryIsLoading
|
||||||
|
|
|
@ -7,10 +7,27 @@ import utils 1.0
|
||||||
ChartStoreBase {
|
ChartStoreBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/*required*/ property string address: ""
|
readonly property alias address: d.address
|
||||||
|
readonly property alias tokenSymbol: d.tokenSymbol
|
||||||
|
readonly property alias currencySymbol: d.currencySymbol
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
// Data identity received from backend
|
||||||
|
property var chainIds: []
|
||||||
|
property string address
|
||||||
|
property string tokenSymbol
|
||||||
|
property string currencySymbol
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasData(address, tokenSymbol, currencySymbol, timeRangeEnum) {
|
||||||
|
return address === d.address && tokenSymbol === d.tokenSymbol && currencySymbol === d.currencySymbol
|
||||||
|
&& root.dataRange[root.timeRangeEnumToTimeIndex(timeRangeEnum)][root.timeRangeEnumToStr(timeRangeEnum)].length > 0
|
||||||
|
}
|
||||||
|
|
||||||
/// \arg timeRange: of type ChartStoreBase.TimeRange
|
/// \arg timeRange: of type ChartStoreBase.TimeRange
|
||||||
function setData(timeRange, timeRangeData, balanceData) {
|
function setData(address, tokenSymbol, currencySymbol, timeRange, timeRangeData, balanceData) {
|
||||||
switch(timeRange) {
|
switch(timeRange) {
|
||||||
case ChartStoreBase.TimeRange.Weekly:
|
case ChartStoreBase.TimeRange.Weekly:
|
||||||
root.weeklyData = balanceData
|
root.weeklyData = balanceData
|
||||||
|
@ -39,38 +56,42 @@ ChartStoreBase {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn("Invalid or unsupported time range")
|
console.warn("Invalid or unsupported time range")
|
||||||
break;
|
return
|
||||||
}
|
|
||||||
root.newDataReady(timeRange)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \arg timeRange: of type ChartStoreBase.TimeRange
|
d.address = address
|
||||||
function resetData(timeRange) {
|
d.tokenSymbol = tokenSymbol
|
||||||
root.setData(timeRange, [], [])
|
d.currencySymbol = currencySymbol
|
||||||
|
|
||||||
|
root.newDataReady(address, tokenSymbol, currencySymbol, timeRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAllData(address, tokenSymbol, currencySymbol) {
|
||||||
|
for (let tR = ChartStoreBase.TimeRange.Weekly; tR <= ChartStoreBase.TimeRange.All; tR++) {
|
||||||
|
root.setData(address, tokenSymbol, currencySymbol, tR, [], [])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: walletSectionAllTokens
|
target: walletSectionAllTokens
|
||||||
function onTokenBalanceHistoryDataReady(balanceHistory: string) {
|
|
||||||
// chainId, address, symbol, timeInterval
|
function onTokenBalanceHistoryDataReady(balanceHistoryJson: string) {
|
||||||
let response = JSON.parse(balanceHistory)
|
// chainIds, address, tokenSymbol, currencySymbol, timeInterval
|
||||||
if (response === null) {
|
let response = JSON.parse(balanceHistoryJson)
|
||||||
console.warn("error parsing balance history json message data")
|
if(typeof response.error !== "undefined") {
|
||||||
root.resetRequestTime()
|
console.warn("error in balance history: " + response.error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(d.address != response.address || d.tokenSymbol != response.tokenSymbol || d.currencySymbol != response.currencySymbol) {
|
||||||
|
root.resetAllData(response.address, response.tokenSymbol, response.currencySymbol)
|
||||||
|
}
|
||||||
|
|
||||||
if(typeof response.historicalData === "undefined" || response.historicalData === null || response.historicalData.length == 0) {
|
if(typeof response.historicalData === "undefined" || response.historicalData === null || response.historicalData.length == 0) {
|
||||||
console.warn("error no data in balance history. Must be an error from status-go")
|
console.info("no data in balance history")
|
||||||
root.resetRequestTime()
|
|
||||||
return
|
|
||||||
} else if(response.address !== root.address) {
|
|
||||||
// Ignore data for other addresses. Will be handled by other instances of this store
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
root.resetData(response.timeInterval)
|
|
||||||
|
|
||||||
var tmpTimeRange = []
|
var tmpTimeRange = []
|
||||||
var tmpDataValues = []
|
var tmpDataValues = []
|
||||||
for(let i = 0; i < response.historicalData.length; i++) {
|
for(let i = 0; i < response.historicalData.length; i++) {
|
||||||
|
@ -81,11 +102,10 @@ ChartStoreBase {
|
||||||
: LocaleUtils.getMonthYear(dataEntry.time * 1000)
|
: LocaleUtils.getMonthYear(dataEntry.time * 1000)
|
||||||
tmpTimeRange.push(dateString)
|
tmpTimeRange.push(dateString)
|
||||||
|
|
||||||
tmpDataValues.push(parseFloat(globalUtils.wei2Eth(dataEntry.value, 18)))
|
tmpDataValues.push(dataEntry.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
root.setData(response.timeInterval, tmpTimeRange, tmpDataValues)
|
root.setData(response.address, response.tokenSymbol, response.currencySymbol, response.timeInterval, tmpTimeRange, tmpDataValues)
|
||||||
root.updateRequestTime(response.timeInterval)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,6 @@ singleton RootStore 1.0 RootStore.qml
|
||||||
CurrenciesStore 1.0 CurrenciesStore.qml
|
CurrenciesStore 1.0 CurrenciesStore.qml
|
||||||
TransactionStore 1.0 TransactionStore.qml
|
TransactionStore 1.0 TransactionStore.qml
|
||||||
BIP39_en 1.0 BIP39_en.qml
|
BIP39_en 1.0 BIP39_en.qml
|
||||||
|
ChartStoreBase 1.0 ChartStoreBase.qml
|
||||||
TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml
|
TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml
|
||||||
TokenMarketValuesStore 1.0 TokenMarketValuesStore.qml
|
TokenMarketValuesStore 1.0 TokenMarketValuesStore.qml
|
||||||
ChartStoreBase 1.0 ChartStoreBase.qml
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f6b4721c4a74f05049b1309c3250ed803d936c59
|
Subproject commit f4f6b253022275dbe08f27d3a29b6c86da21b35b
|
Loading…
Reference in New Issue