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:
Stefan 2022-11-15 23:48:59 +02:00 committed by Stefan Dunca
parent 7ebe5488bd
commit 3bb667bb7a
25 changed files with 603 additions and 254 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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",
} }
} }

View File

@ -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,

View File

@ -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))

View File

@ -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.} =

View File

@ -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)

View File

@ -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.}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"):

View File

@ -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(), [&params](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(), [&params](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(), [&params](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(), [&params](const auto& t) { return t.symbol == params.token; });
ASSERT_NE(tokenIt, tokens.end());
}
}
const auto newAccountIt = std::find_if(updatedAccounts.begin(), updatedAccounts.end(), [&params](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

View File

@ -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) {

View File

@ -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() }
}
} }
} }
} }

View File

@ -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()
} }
} }
} }

View File

@ -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

View File

@ -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)
} }
} }
} }

View File

@ -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

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit f6b4721c4a74f05049b1309c3250ed803d936c59 Subproject commit f4f6b253022275dbe08f27d3a29b6c86da21b35b