From 5c205be8440f964988be4bc6d273c18b5cf06dde Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 27 Feb 2020 13:51:48 -0500 Subject: [PATCH] release (#371) * Fabo/fix gas (#250) * fix gas estimate * linted * fixed test * do not keep data sources (#251) * track failing transactions in Sentry (#249) * correctly set the tx schema for a failing tx (#248) * Fabo/remove per block caching as not working (#247) * remove per block caching as not working * fix memoized results Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> * delete perblockcachedatasource (#253) * Ana/fix balances in actionmodal (#255) * fix action modal available balance * include regen * use dictionary for denomlookup * use correct events for received txs (#257) * enable account creation for some networks (#252) * network update time metric added (#256) * network update time metric added * added missing dep Co-authored-by: Fabian * Fix proposal deposit (#261) * Remove denom handling from getDeposit() * Revert undesired change * delete package-lock.json * localtestnet config change (#265) * Ana/handle "address not from this network" error (#263) * add check address function for all queries * apply suggestions * Ana/add fiatvalue to balances query (e-Money) (#262) * preparation * more preparation * add fiatvalue field to balances query * fix get account info * apply suggestions * apply one last suggestion * suggestions+ Co-authored-by: Fabian * Ana/emoney fix expected returns with inflation and totalbacked (#243) * fix expected returns with inflation and supply * minor fixes. dictionary * query exchange rates from emoney api * fix infinite expected returns * convert api url to const * add eur value to totalbackedvalue. totalngm gains * add important comment * finish calculation * lint * catch errors with sentry Co-authored-by: Fabian * readd coin conversion (#268) * delete amount field (#274) * Fabo/increase gas again (#271) * icrease gas again * fixed test * Fabo/load all txs (even if more then first page in response) (#270) * load all txs (even if more then first page in response) * improved handling of txs * missing renaming * fixed paginated load * add pagination fix also to cosmosV0-source Co-authored-by: iambeone Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> * fixing issue with multiple senders in one event (#273) * fixing issue with multiple senders in one event * Update lib/source/cosmosV2-source.js Co-authored-by: Fabian * Fabo/allow signing for terra + emoney (#267) * allow signing for terra * readd coin conversion * enable actions for terra * fix correct terra testnet url * comments and guards * enabled more txs for emoney and fixed broadcasting * added a catch for wrongly formatted broadcast urls * recover default field. change some network titles (#277) * Fabo/add network data to API (#278) * non desctructive introduction of better address prefix wording * added address creator to API * adjusted test * added ledger app to networks config * add icon property to schema (#281) * add icon property to schema * fix network schema validation Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> * filter out validator specific txs (#279) * Ana/balances coinreducer good fix (#269) * balances coinreducer good fix * refactored fiat value logic Co-authored-by: Fabian * Create network_integration.md * Update network_integration.md * Update network_integration.md * Fabo/avoid 500 errors (#288) * avoid using the latest query * cleanup * Ana/filter validator tx cross network and add txvalue reducer (#285) * filter validators cross network * add value reducer. necessary for multi claim txs * add validator txs filter also for cosmosv0 source * filter and make array only claim rewards msg value * filter txs by whitelist * change length in multi claim reward reducer * add withdrawvalidators * replace dictionary for set * refactor transaction snippet. avoid repetition * Ana/emoney upgrade (mergeable) (#282) * update emoney api_url * fix denom. add default fiat currency * fix rpc endpoint * fix value (my bad) (#293) * fix value (my bad) * trigger another ci flow * erase space * set correct new chain id (#294) * restart API * restart API * fix pr alert (#297) * Fabo/298 tendermint reconnect (#300) * reconnect on tendermint disconnect * cleanup * comments * Update cosmos-node-subscription.js * Fabo/299 trigger a chain hangup error (#301) * trigger a chain hangup error * increase chain hangup time * Apply suggestions from code review * Fabo/store validator addresses (#296) * add validator addresses to db * linted * ignore in local dev * revert * fixed fetch * comment * refactored db into constructor * cleanup * add clearTimeout to avoid reconnection hell (#306) * add clearTimeout to avoid reconnection hell * removed console.log * Aleksei/luniedb replaced (#303) * add validator addresses to db * linted * ignore in local dev * revert * fixed fetch * comment * refactored db into constructor * cleanup * replaced luniedb * linted Co-authored-by: Fabian * disable reconnection logic * clear polling interval for tendermint connection * simple api fixes (#310) * Fabo/remove tendermint (#311) * remove tendermint * fixed empty blockHeight issue * small refactoring * catch on fetches to get logging * delay block updates * add retry logic * refactored getBlockByHeight * remove pm2 dep * validator profiles were returned as array (#312) * remove pr github action (#316) * fixing caching issue and more (#315) * fixing caching issue and more * clear chain hangup timeout * timeout fixed * Update lib/block-listeners/cosmos-node-subscription.js Co-authored-by: Jordan Bibla * added terra_mainnet to networks (#309) * added terra_mainnet to networks * errors fix reverted * Ana/add multidenom rewards. necessary for emoney (#308) * add multidenom rewards. necessary for emoney * add rewards to overview. improve code location * lint * add filter rewards for multidenom * important fix * another important fix * fix cannot read propery constructor of undefined (#318) * change terra mainnet title * pushing something to trigger new build and to restart the node * trying out nylira's node * WIP: Ana/More Terra fixes (#323) * fixing my mess * move all rewards logic to terra * clearly signal single denom rewards * Revert "WIP: Ana/More Terra fixes (#323)" (#325) This reverts commit 408186a3ef743ca51d513eb91c937f49960e7492. * catch inside block polling function (#327) * Fabo/disable writing validator addresses for now (#329) * disable writing validator addresses for now * linted * flushing http cache (#332) * Transaction abstraction schema (#333) * tx abstraction proposal using union * Use Coin type * Cleanup, TransactionV2 * Cleanup, not multisend tx * Unsupported tx (like multisend) don't have details * Fabo/switch to docker swarm mode (#330) * swtich to docker swarm mode * typo * Update .github/workflows/development.yml * Ana/more terra fixes (***THE COMEBACK***) (#324) * fixing my mess * move all rewards logic to terra * clearly signal single denom rewards * error structure has changed. need to look into it * also return denom for single denom rewards * manage errors properly * delete comments * Update lib/reducers/terraV3-reducers.js * loop through transaction messages * doing crazy stuff * rewrite the crazy bit Co-authored-by: Fabian * cosmosv0 was missing the retry logic (#334) * add back validator to db logic (#335) * path_prefix added to network data (#338) * path_prefix added to network data * path_prefix replaced to slug * tests fixed * remove regen (#337) Co-authored-by: Jordan Bibla * Fabo+Mario/transaction abstraction (#336) * draft for transaction abstraction * use lunie message types * Add supported tx types * WIP * Draft new reducers * Fix msg types * Need to resolve type for TransactionDetails union * debug resolver * lint * Fix * Cleanup * fix some union issues * Almost done * Small fixes * Add userTransactionAddedV2 * Add userTransactionAddedV2 to resolvers Co-authored-by: Mario Pino * emoney fixes for fiatvalue (#345) * emoney fixes for fiatvalue * refactor with coinreducer * speaking functions are your friends * refactor ugly nested code * lint * add denoms lookups for terra and emoney (#346) * Ana/add only tokens gas prices 2nd attempt (#344) * add terra and emoney gas price. terra reducer * delete fiatvalue from get balances in cosmos * add emoney reducer * hyper important emoney fixes * fix for emoney denoms * change gas price to micro units * add emoney denoms to denomlookup * transform to microunit also for terra tokens * return null for emoney gas prices * add harcoded gas prices * correct emoney hardcoded values * hardcoded terra gas prices. not working * update hardcoded values to working ones * delete unrelated changes to gas prices * apply suggestions except coinreducer * they call me mr coinreducer * change amount for price. add gas price reducer * change naming in gas price reducer * add error message Co-authored-by: Fabian * fix rewards denoms (#351) * updating image on deploy (#352) * fix my mess once more (#353) * Ana/add gas prices to other networks (#350) * add gasprices to cosmos * format gas prices to three decimals * Apply suggestions from code review * Update lib/reducers/cosmosV0-reducers.js Co-authored-by: Fabian Co-authored-by: Jordan Bibla * added a tx success push, fallback (#354) * fix cosmos gas price (#355) * fix terra for new tmbalance (#358) * Fabo/new emoney rewards (#357) * intent to fix the emoney rewards * intent to fix emoney rewards * simplified code * comments * hack to fix reduce function * eligable -> eligible * correct reducer fix Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> Co-authored-by: Jordan Bibla * Aleksei/prevent constant db calls (#304) * add validator addresses to db * linted * ignore in local dev * revert * fixed fetch * comment * refactored db into constructor * cleanup * prevent constant db interaction * stored object structure changed * remove nesting in validators * filter validators list Co-authored-by: Fabian * limit txs pages load by two pages per request (#314) * limit txs pages load by two pages per request * delete block from loadpaginatedtxs * small fixes * Update lib/source/cosmosV2-source.js * linted Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> Co-authored-by: Fabian * linted * linted * Fabo/Use figment nodes (#362) * use api keys * add env variables for nodes * hard code api keys (#365) * fix ngm fiatvalue (#361) * hardcoded urls fix (#366) * WIP: Improve TransactionV2 implementation (#349) * Add from field to SendTx type * add amount to claim rewards transactions * convert to units in string coin reducer * fix typo * add multidenom string coinreducer for claim amount * Add UnknownTx to schema * Add BlockV2 type which returns TransactionV2 txs * Add blockV2 query that returns TransactionV2 txs * kill handling claim rewards amount * Update lib/reducers/cosmosV0-reducers.js Co-Authored-By: Fabian * Update lib/reducers/cosmosV0-reducers.js Co-Authored-By: Fabian * Cleanup * lint * Support claim rewards from multiple validators * Fix, cleanup Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> Co-authored-by: Fabian * Aleksei/fix db insert (#367) * fixed insert function to allow empty schema * Aleksei/statistics in the api (#364) * collecting statistics in the API * small fixes * fixes * changing address to key in clearOverviewedAddresses Co-authored-by: Aleksey Rudometov * add pageNumber to schema (#370) * add pageNumber to schema * file wasn't saved * trigger ci Co-authored-by: Ana G. <40721795+Bitcoinera@users.noreply.github.com> Co-authored-by: Aleksey Rudometov Co-authored-by: Mario Pino Co-authored-by: Jordan Bibla --- .github/workflows/production.yml | 2 +- README.md | 2 +- data/networks.js | 208 ++++++++++++++++++++++++++++ data/networks.json | 206 --------------------------- lib/apollo.js | 5 + lib/controller/transaction/index.js | 24 +++- lib/database/helpers.js | 4 +- lib/database/index.js | 9 +- lib/database/methods.js | 14 +- lib/message-types.js | 2 +- lib/networks.js | 2 +- lib/reducers/cosmosV0-reducers.js | 36 +++-- lib/resolvers.js | 17 ++- lib/routes/transaction.js | 53 +++---- lib/schema.js | 26 +++- lib/source/cosmosV0-source.js | 33 ++++- lib/source/cosmosV2-source.js | 15 +- lib/source/emoneyV0-source.js | 2 +- lib/statistics.js | 154 ++++++++++++++++++++ 19 files changed, 551 insertions(+), 263 deletions(-) create mode 100644 data/networks.js delete mode 100644 data/networks.json create mode 100644 lib/statistics.js diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index f775579143..9f63240190 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -25,6 +25,6 @@ jobs: - name: Installing Docker Compose run: ssh root@167.71.107.214 "sudo curl -L \"https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)\" -o /usr/local/bin/docker-compose; sudo chmod +x /usr/local/bin/docker-compose" - name: Run on Digital Ocean - run: ssh root@167.71.107.214 "docker image prune -a -f; docker load < lunieapi.tgz;export HASURA_URL="https://production-db.lunie.io/v1/graphql"; export HASURA_ADMIN_KEY="${{ secrets.LUNIE_PRODUCTION_DB_KEY }}"; export SENTRY_DSN="${{ secrets.SENTRY_DSN_PRODUCTION }}"; docker stack deploy -c docker-compose.yml lunieapi; docker service update --image lunieapi:latest --force lunieapi_lunieapi" + run: ssh root@167.71.107.214 "docker image prune -a -f; docker load < lunieapi.tgz;export HASURA_URL="https://production-db.lunie.io/v1/graphql"; export HASURA_ADMIN_KEY="${{ secrets.LUNIE_PRODUCTION_DB_KEY }}"; export SENTRY_DSN="${{ secrets.SENTRY_DSN_PRODUCTION }}"; export FIGMENT_COSMOS_HUB_API_KEY="${{ secrets.FIGMENT_COSMOS_HUB_API_KEY }}"; export FIGMENT_GAIA_13007_API_KEY="${{ secrets.FIGMENT_GAIA_13007_API_KEY }}"; docker stack deploy -c docker-compose.yml lunieapi; docker service update --image lunieapi:latest --force lunieapi_lunieapi" - name: Setting up cron job for pm2 metrics export run: ssh root@167.71.107.214 "mkdir /logs -p; touch /logs/show; line='*/1 * * * * docker exec lunieapi pm2 show 0 > /logs/show; perl /root/pm2metrics.pl'; crontab -l | grep -q 'lunieapi pm2' && true || (crontab -l; echo "$line" ) | crontab -" diff --git a/README.md b/README.md index 7e7db2e177..d26c1c6791 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Installation +## Installation Clone the repository: diff --git a/data/networks.js b/data/networks.js new file mode 100644 index 0000000000..498febe6d2 --- /dev/null +++ b/data/networks.js @@ -0,0 +1,208 @@ +module.exports = [ + { + id: 'cosmos-hub-testnet', + title: 'Gaia Testnet', + chain_id: 'gaia-13007', + rpc_url: 'wss://gaia-13007.lunie.io:26657/websocket', + api_url: + 'http://gaia-13007--lcd--archive.datahub.figment.network/apikey/124a234fb3c6bccf430bfd298bd160ae', + bech32_prefix: 'cosmos', + address_prefix: 'cosmos', + address_creator: 'cosmos', + ledger_app: 'cosmos', + source_class_name: 'source/cosmosV2-source', + block_listener_class_name: 'block-listeners/cosmos-node-subscription', + testnet: true, + feature_session: true, + feature_explore: true, + feature_portfolio: true, + feature_validators: true, + feature_proposals: true, + feature_activity: true, + feature_explorer: true, + action_send: true, + action_claim_rewards: true, + action_delegate: true, + action_redelegate: true, + action_undelegate: true, + action_deposit: true, + action_vote: true, + action_proposal: true, + default: false, + stakingDenom: 'MUON', + enabled: true, + icon: 'https://app.lunie.io/img/networks/cosmos-hub-mainnet.png', + slug: 'cosmos-hub-testnet' + }, + { + id: 'cosmos-hub-mainnet', + title: 'Cosmos Hub', + chain_id: 'cosmoshub-3', + rpc_url: 'wss://cosmos-hub-3.lunie.io/websocket', + api_url: + 'https://cosmoshub-3--lcd--archive.datahub.figment.network/apikey/7996efcecdc5ef91da3715eb60cd2201', + bech32_prefix: 'cosmos', + address_prefix: 'cosmos', + address_creator: 'cosmos', + ledger_app: 'cosmos', + source_class_name: 'source/cosmosV2-source', + block_listener_class_name: 'block-listeners/cosmos-node-subscription', + testnet: false, + feature_session: true, + feature_explore: true, + feature_portfolio: true, + feature_validators: true, + feature_proposals: true, + feature_activity: true, + feature_explorer: true, + action_send: true, + action_claim_rewards: true, + action_delegate: true, + action_redelegate: true, + action_undelegate: true, + action_deposit: true, + action_vote: true, + action_proposal: true, + default: true, + stakingDenom: 'ATOM', + enabled: true, + icon: 'https://app.lunie.io/img/networks/cosmos-hub-mainnet.png', + slug: 'cosmos-hub' + }, + { + id: 'terra-mainnet', + title: 'Terra', + chain_id: 'columbus-3', + api_url: 'https://fcd.terra.dev/', + rpc_url: 'ws://52.78.43.42:26657/websocket', + bech32_prefix: 'terra', + address_prefix: 'terra', + address_creator: 'cosmos', + ledger_app: 'cosmos', + source_class_name: 'source/terraV3-source', + block_listener_class_name: 'block-listeners/cosmos-node-subscription', + testnet: false, + feature_session: true, + feature_explore: true, + feature_portfolio: true, + feature_validators: true, + feature_proposals: true, + feature_activity: true, + feature_explorer: true, + action_send: true, + action_claim_rewards: true, + action_delegate: true, + action_redelegate: true, + action_undelegate: true, + action_deposit: false, + action_vote: false, + action_proposal: false, + default: false, + stakingDenom: 'LUNA', + enabled: true, + icon: 'https://app.lunie.io/img/networks/terra-mainnet.png', + slug: 'terra' + }, + { + id: 'terra-testnet', + title: 'Terra Testnet', + chain_id: 'soju-0013', + api_url: 'https://terra-testnet.lunie.io', + rpc_url: 'wss://terra-testnet.lunie.io/websocket', + bech32_prefix: 'terra', + address_prefix: 'terra', + address_creator: 'cosmos', + ledger_app: 'cosmos', + source_class_name: 'source/terraV3-source', + block_listener_class_name: 'block-listeners/cosmos-node-subscription', + testnet: true, + feature_session: true, + feature_explore: true, + feature_portfolio: true, + feature_validators: true, + feature_proposals: false, + feature_activity: true, + feature_explorer: true, + action_send: true, + action_claim_rewards: true, + action_delegate: true, + action_redelegate: true, + action_undelegate: true, + action_deposit: false, + action_vote: false, + action_proposal: false, + default: false, + stakingDenom: 'LUNA', + enabled: true, + icon: 'https://app.lunie.io/img/networks/terra-testnet.png', + slug: 'terra-testnet' + }, + { + id: 'emoney-testnet', + title: 'e-Money Testnet', + chain_id: 'lilmermaid-5', + api_url: 'http://lilmermaid.validator.network/light', + rpc_url: 'wss://lilmermaid.validator.network/websocket', + bech32_prefix: 'emoney', + address_prefix: 'emoney', + address_creator: 'cosmos', + ledger_app: 'cosmos', + source_class_name: 'source/emoneyV0-source', + block_listener_class_name: 'block-listeners/cosmos-node-subscription', + testnet: true, + feature_session: true, + feature_explore: true, + feature_portfolio: true, + feature_validators: true, + feature_proposals: false, + feature_activity: true, + feature_explorer: true, + action_send: true, + action_claim_rewards: true, + action_delegate: true, + action_redelegate: true, + action_undelegate: true, + action_deposit: false, + action_vote: false, + action_proposal: false, + default: false, + stakingDenom: 'NGM', + enabled: true, + icon: 'https://app.lunie.io/img/networks/emoney-testnet.png', + slug: 'emoney-testnet' + }, + { + id: 'livepeer-mainnet', + title: 'Livepeer', + chain_id: 'ethereum-1', + api_url: 'https://livepeer-mainnet.lunie.io/', + rpc_url: 'wss://livepeer-mainnet.lunie.io/websocket', + bech32_prefix: '0x', + address_prefix: '0x', + address_creator: 'ethereum', + ledger_app: 'ethereum', + source_class_name: 'source/livepeerV0-source', + block_listener_class_name: 'block-listeners/livepeer-node-polling', + testnet: false, + feature_session: false, + feature_explore: true, + feature_portfolio: false, + feature_validators: true, + feature_proposals: false, + feature_activity: false, + feature_explorer: false, + action_send: false, + action_claim_rewards: false, + action_delegate: false, + action_redelegate: false, + action_undelegate: false, + action_deposit: false, + action_vote: false, + action_proposal: false, + default: false, + stakingDenom: 'LPT', + enabled: false, + icon: 'https://app.lunie.io/img/networks/livepeer-mainnet.png', + slug: 'livepeer' + } +] diff --git a/data/networks.json b/data/networks.json deleted file mode 100644 index 5136cefc9b..0000000000 --- a/data/networks.json +++ /dev/null @@ -1,206 +0,0 @@ -[ - { - "id": "cosmos-hub-testnet", - "title": "Gaia Testnet", - "chain_id": "gaia-13007", - "rpc_url": "wss://gaia-13007.lunie.io:26657/websocket", - "api_url": "https://gaia-13007.lunie.io", - "bech32_prefix": "cosmos", - "address_prefix": "cosmos", - "address_creator": "cosmos", - "ledger_app": "cosmos", - "source_class_name": "source/cosmosV2-source", - "block_listener_class_name": "block-listeners/cosmos-node-subscription", - "testnet": true, - "feature_session": true, - "feature_explore": true, - "feature_portfolio": true, - "feature_validators": true, - "feature_proposals": true, - "feature_activity": true, - "feature_explorer": true, - "action_send": true, - "action_claim_rewards": true, - "action_delegate": true, - "action_redelegate": true, - "action_undelegate": true, - "action_deposit": true, - "action_vote": true, - "action_proposal": true, - "default": false, - "stakingDenom": "MUON", - "enabled": true, - "icon": "https://app.lunie.io/img/networks/cosmos-hub-mainnet.png", - "slug": "cosmos-hub-testnet" - }, - { - "id": "cosmos-hub-mainnet", - "title": "Cosmos Hub", - "chain_id": "cosmoshub-3", - "rpc_url": "wss://cosmos-hub-3.lunie.io/websocket", - "api_url": "https://lcd.nylira.net", - "bech32_prefix": "cosmos", - "address_prefix": "cosmos", - "address_creator": "cosmos", - "ledger_app": "cosmos", - "source_class_name": "source/cosmosV2-source", - "block_listener_class_name": "block-listeners/cosmos-node-subscription", - "testnet": false, - "feature_session": true, - "feature_explore": true, - "feature_portfolio": true, - "feature_validators": true, - "feature_proposals": true, - "feature_activity": true, - "feature_explorer": true, - "action_send": true, - "action_claim_rewards": true, - "action_delegate": true, - "action_redelegate": true, - "action_undelegate": true, - "action_deposit": true, - "action_vote": true, - "action_proposal": true, - "default": true, - "stakingDenom": "ATOM", - "enabled": true, - "icon": "https://app.lunie.io/img/networks/cosmos-hub-mainnet.png", - "slug": "cosmos-hub" - }, - { - "id": "terra-mainnet", - "title": "Terra", - "chain_id": "columbus-3", - "api_url": "https://fcd.terra.dev/", - "rpc_url": "ws://52.78.43.42:26657/websocket", - "bech32_prefix": "terra", - "address_prefix": "terra", - "address_creator": "cosmos", - "ledger_app": "cosmos", - "source_class_name": "source/terraV3-source", - "block_listener_class_name": "block-listeners/cosmos-node-subscription", - "testnet": false, - "feature_session": true, - "feature_explore": true, - "feature_portfolio": true, - "feature_validators": true, - "feature_proposals": true, - "feature_activity": true, - "feature_explorer": true, - "action_send": true, - "action_claim_rewards": true, - "action_delegate": true, - "action_redelegate": true, - "action_undelegate": true, - "action_deposit": false, - "action_vote": false, - "action_proposal": false, - "default": false, - "stakingDenom": "LUNA", - "enabled": true, - "icon": "https://app.lunie.io/img/networks/terra-mainnet.png", - "slug": "terra" - }, - { - "id": "terra-testnet", - "title": "Terra Testnet", - "chain_id": "soju-0013", - "api_url": "https://terra-testnet.lunie.io", - "rpc_url": "wss://terra-testnet.lunie.io/websocket", - "bech32_prefix": "terra", - "address_prefix": "terra", - "address_creator": "cosmos", - "ledger_app": "cosmos", - "source_class_name": "source/terraV3-source", - "block_listener_class_name": "block-listeners/cosmos-node-subscription", - "testnet": true, - "feature_session": true, - "feature_explore": true, - "feature_portfolio": true, - "feature_validators": true, - "feature_proposals": false, - "feature_activity": true, - "feature_explorer": true, - "action_send": true, - "action_claim_rewards": true, - "action_delegate": true, - "action_redelegate": true, - "action_undelegate": true, - "action_deposit": false, - "action_vote": false, - "action_proposal": false, - "default": false, - "stakingDenom": "LUNA", - "enabled": true, - "icon": "https://app.lunie.io/img/networks/terra-testnet.png", - "slug": "terra-testnet" - }, - { - "id": "emoney-testnet", - "title": "e-Money Testnet", - "chain_id": "lilmermaid-5", - "api_url": "http://lilmermaid.validator.network/light", - "rpc_url": "wss://lilmermaid.validator.network/websocket", - "bech32_prefix": "emoney", - "address_prefix": "emoney", - "address_creator": "cosmos", - "ledger_app": "cosmos", - "source_class_name": "source/emoneyV0-source", - "block_listener_class_name": "block-listeners/cosmos-node-subscription", - "testnet": true, - "feature_session": true, - "feature_explore": true, - "feature_portfolio": true, - "feature_validators": true, - "feature_proposals": false, - "feature_activity": true, - "feature_explorer": true, - "action_send": true, - "action_claim_rewards": true, - "action_delegate": true, - "action_redelegate": true, - "action_undelegate": true, - "action_deposit": false, - "action_vote": false, - "action_proposal": false, - "default": false, - "stakingDenom": "NGM", - "enabled": true, - "icon": "https://app.lunie.io/img/networks/emoney-testnet.png", - "slug": "emoney-testnet" - }, - { - "id": "livepeer-mainnet", - "title": "Livepeer", - "chain_id": "ethereum-1", - "api_url": "https://livepeer-mainnet.lunie.io/", - "rpc_url": "wss://livepeer-mainnet.lunie.io/websocket", - "bech32_prefix": "0x", - "address_prefix": "0x", - "address_creator": "ethereum", - "ledger_app": "ethereum", - "source_class_name": "source/livepeerV0-source", - "block_listener_class_name": "block-listeners/livepeer-node-polling", - "testnet": false, - "feature_session": false, - "feature_explore": true, - "feature_portfolio": false, - "feature_validators": true, - "feature_proposals": false, - "feature_activity": false, - "feature_explorer": false, - "action_send": false, - "action_claim_rewards": false, - "action_delegate": false, - "action_redelegate": false, - "action_undelegate": false, - "action_deposit": false, - "action_vote": false, - "action_proposal": false, - "default": false, - "stakingDenom": "LPT", - "enabled": false, - "icon": "https://app.lunie.io/img/networks/livepeer-mainnet.png", - "slug": "livepeer" - } -] \ No newline at end of file diff --git a/lib/apollo.js b/lib/apollo.js index 73f2749b4d..e29644637f 100644 --- a/lib/apollo.js +++ b/lib/apollo.js @@ -48,6 +48,11 @@ function createApolloServer(httpServer) { plugins: [responseCachePlugin()], subscriptions: { path: config.subscriptionPath + }, + context: fingerprintContext => { + if (fingerprintContext.req) { + return { fingerprint: fingerprintContext.req.headers.fingerprint } + } } } diff --git a/lib/controller/transaction/index.js b/lib/controller/transaction/index.js index d35af23f9c..5ca35c647f 100644 --- a/lib/controller/transaction/index.js +++ b/lib/controller/transaction/index.js @@ -3,6 +3,13 @@ const { networkMap } = require('../../networks') const Sentry = require('@sentry/node') const { publishUserTransactionAdded } = require('../../subscriptions') const reducers = require('../../reducers/cosmosV0-reducers') // TODO the whole transaction service only works for cosmos rn +const { + prestore, + storePrestored, + defineActionType, + defineActionDenom, + defineActionValue +} = require('../../statistics') global.fetch = require('node-fetch') @@ -33,7 +40,7 @@ async function estimate() { } } -async function broadcast(tx) { +async function broadcast(tx, fingerprint) { console.log(`Received broadcast: ${JSON.stringify(tx)}`) try { const hash = await broadcastTransaction( @@ -42,6 +49,18 @@ async function broadcast(tx) { networkMap[tx.networkId].api_url, tx.signedMessage ) + // presaving to the database + prestore( + { + network: tx.networkId, + address: tx.senderAddress, + action: defineActionType(tx.signedMessage.msg[0].type), + value: defineActionValue(tx.signedMessage.msg), + denom: defineActionDenom(tx.signedMessage.msg), + fingerprint + }, + hash + ) return { hash: hash, success: true @@ -193,6 +212,9 @@ async function pollTransactionSuccess( // but also here as a fallback // TODO the client might now update twice as it receives the success twice, could be fine though const transaction = reducers.transactionReducer(res, reducers) + // store in db + storePrestored(hash) + // we need to call publishUserTransactionAdded(networkId, senderAddress, transaction) } catch (error) { console.error('TX failed:', hash, error) diff --git a/lib/database/helpers.js b/lib/database/helpers.js index fcbadf8f06..9833d66614 100644 --- a/lib/database/helpers.js +++ b/lib/database/helpers.js @@ -65,9 +65,11 @@ const insert = ( return } + let schema_prefix = schema ? schema + '_' : '' + const query = ` mutation { - insert_${schema}_${table} ( + insert_${schema_prefix}${table} ( objects: ${stringifyForGraphQL(rows, height, chainId)}${ upsert ? `, diff --git a/lib/database/index.js b/lib/database/index.js index 0594e96aa0..2a2a1a9960 100644 --- a/lib/database/index.js +++ b/lib/database/index.js @@ -1,5 +1,9 @@ const { insert, read } = require('./helpers') -const { getValidatorsInfo, getMaintenance } = require('./methods') +const { + getValidatorsInfo, + getMaintenance, + storeStatistics +} = require('./methods') function database({ hasura_url, hasura_admin_key }) { return schema => { @@ -17,6 +21,9 @@ function database({ hasura_url, hasura_admin_key }) { })(schema)(validatorId) return validatorInfo[0] }, + storeStatistics: storeStatistics({ hasura_url, hasura_admin_key })( + schema + ), getMaintenance: getMaintenance({ hasura_url, hasura_admin_key diff --git a/lib/database/methods.js b/lib/database/methods.js index 326169d3e5..24bf5d72e7 100644 --- a/lib/database/methods.js +++ b/lib/database/methods.js @@ -1,4 +1,4 @@ -const { read } = require('./helpers') +const { read, insert } = require('./helpers') const getValidatorsInfo = ({ hasura_url, @@ -14,6 +14,15 @@ const getValidatorsInfo = ({ validatorId ? `where: {operator_address: {_eq: "${validatorId}"}}` : false ) } +const storeStatistics = ({ + hasura_url, + hasura_admin_key +}) => schema => async payload => { + return await insert({ + hasura_url, + hasura_admin_key + })(schema)(`statistics`, payload) +} const getMaintenance = ({ hasura_url, hasura_admin_key @@ -30,5 +39,6 @@ const getMaintenance = ({ } module.exports = { getValidatorsInfo, - getMaintenance + getMaintenance, + storeStatistics } diff --git a/lib/message-types.js b/lib/message-types.js index 0265445da1..05f16e52e7 100644 --- a/lib/message-types.js +++ b/lib/message-types.js @@ -34,7 +34,7 @@ const lunieMessageTypes = { UNSTAKE: `UnstakeTx`, VOTE: `VoteTx`, DEPOSIT: `DepositTx`, - WITHDRAW_DELEGATION_REWARDS: `ClaimRewardsTx`, + CLAIM_REWARDS: `ClaimRewardsTx`, SUBMIT_PROPOSAL: `SubmitProposalTx`, UNKNOWN: `UnknownTx` } diff --git a/lib/networks.js b/lib/networks.js index 2fa6745dce..629614edf5 100644 --- a/lib/networks.js +++ b/lib/networks.js @@ -1,6 +1,6 @@ const { keyBy } = require('lodash') const config = require('../config') -const networksMain = require('../data/networks.json') +const networksMain = require('../data/networks.js') const networksLocal = require('../data/networks-local.js') let networks = networksMain diff --git a/lib/reducers/cosmosV0-reducers.js b/lib/reducers/cosmosV0-reducers.js index bacb3f0617..738c62b799 100644 --- a/lib/reducers/cosmosV0-reducers.js +++ b/lib/reducers/cosmosV0-reducers.js @@ -435,7 +435,7 @@ function formatTransactionsReducer(txs, reducers) { return reversedTxs.map(tx => transactionReducer(tx, reducers)) } -function transactionReducerV2(transaction, reducers) { +function transactionReducerV2(transaction, reducers, stakingDenom) { // TODO check if this is anywhere not an array let fees if (Array.isArray(transaction.tx.value.fee.amount)) { @@ -451,7 +451,13 @@ function transactionReducerV2(transaction, reducers) { type: getMessageType(type), hash: transaction.txhash, height: transaction.height, - details: transactionDetailsReducer(getMessageType(type), value, reducers), + details: transactionDetailsReducer( + getMessageType(type), + value, + reducers, + transaction, + stakingDenom + ), timestamp: transaction.timestamp, memo: transaction.tx.value.memo, fees, @@ -460,13 +466,15 @@ function transactionReducerV2(transaction, reducers) { return returnedMessages } -function transactionsReducerV2(txs, reducers) { +function transactionsReducerV2(txs, reducers, stakingDenom) { const duplicateFreeTxs = uniqWith(txs, (a, b) => a.txhash === b.txhash) const sortedTxs = sortBy(duplicateFreeTxs, ['timestamp']) const reversedTxs = reverse(sortedTxs) // here we filter out all transactions related to validators return reversedTxs.reduce((collection, transaction) => { - return collection.concat(transactionReducerV2(transaction, reducers)) + return collection.concat( + transactionReducerV2(transaction, reducers, stakingDenom) + ) }, []) } @@ -537,7 +545,7 @@ function getMessageType(type) { case 'MsgUndelegate': return lunieMessageTypes.UNSTAKE case 'MsgWithdrawDelegationReward': - return lunieMessageTypes.WITHDRAW_DELEGATION_REWARDS + return lunieMessageTypes.CLAIM_REWARDS case 'MsgSubmitProposal': return lunieMessageTypes.SUBMIT_PROPOSAL case 'MsgVote': @@ -550,7 +558,7 @@ function getMessageType(type) { } // function to map cosmos messages to our details format -function transactionDetailsReducer(type, message, reducers) { +function transactionDetailsReducer(type, message, reducers, transaction) { let details switch (type) { case lunieMessageTypes.SEND: @@ -565,8 +573,8 @@ function transactionDetailsReducer(type, message, reducers) { case lunieMessageTypes.UNSTAKE: details = unstakeDetailsReducer(message, reducers) break - case lunieMessageTypes.WITHDRAW_DELEGATION_REWARDS: - details = claimRewardsDetailsReducer(message, reducers) + case lunieMessageTypes.CLAIM_REWARDS: + details = claimRewardsDetailsReducer(transaction.tx.value.msg) break case lunieMessageTypes.SUBMIT_PROPOSAL: details = submitProposalDetailsReducer(message, reducers) @@ -589,6 +597,7 @@ function transactionDetailsReducer(type, message, reducers) { function sendDetailsReducer(message, reducers) { return { + from: [message.from_address], to: [message.to_address], amount: reducers.coinReducer(message.amount[0]) } @@ -616,10 +625,15 @@ function unstakeDetailsReducer(message, reducers) { } } -function claimRewardsDetailsReducer(message, reducers) { +function claimRewardsDetailsReducer(messages) { return { - from: [message.validator_address], - amount: reducers.coinReducer(message.amount) + from: messages + .filter(msg => msg.type.split(`/`)[1] === `MsgWithdrawDelegationReward`) + .map(msg => msg.value.validator_address), + amount: { + amount: 0, + denom: `` + } } } diff --git a/lib/resolvers.js b/lib/resolvers.js index 512e05eaec..c0ce5c46a3 100644 --- a/lib/resolvers.js +++ b/lib/resolvers.js @@ -6,6 +6,7 @@ const { formatBech32Reducer } = require('./reducers/livepeerV0-reducers') const { networkList, networkMap } = require('./networks') const database = require('./database') const config = require('../config.js') +const { logOverview } = require('./statistics') function createDBInstance(network) { const networkSchemaName = network ? network.replace(/-/g, '_') : false @@ -221,6 +222,11 @@ const resolvers = { cacheControl.setCacheHint({ maxAge }) return remoteFetch(dataSources, networkId).getBlockByHeight(height) }, + blockV2: (_, { networkId, height }, { dataSources }, { cacheControl }) => { + const maxAge = height ? 60 : 10 + cacheControl.setCacheHint({ maxAge }) + return remoteFetch(dataSources, networkId).getBlockByHeightV2(height) + }, network: (_, { id }) => { const network = networkMap[id] if (network.id === 'local-cosmos-hub-testnet') { @@ -305,7 +311,11 @@ const resolvers = { } return rewards }, - overview: async (_, { networkId, address }, { dataSources }) => { + overview: async ( + _, + { networkId, address }, + { dataSources, fingerprint } + ) => { const validatorsDictionary = localStore(dataSources, networkId).validators const overview = await remoteFetch(dataSources, networkId).getOverview( address, @@ -313,10 +323,11 @@ const resolvers = { ) overview.networkId = networkId overview.address = address + logOverview(overview, fingerprint) return overview }, - transactions: (_, { networkId, address }, { dataSources }) => - remoteFetch(dataSources, networkId).getTransactions(address), + transactions: (_, { networkId, address, pageNumber }, { dataSources }) => + remoteFetch(dataSources, networkId).getTransactions(address, pageNumber), transactionsV2: (_, { networkId, address }, { dataSources }) => remoteFetch(dataSources, networkId).getTransactionsV2(address) }, diff --git a/lib/routes/transaction.js b/lib/routes/transaction.js index fbc6bd184e..dd088c47e8 100644 --- a/lib/routes/transaction.js +++ b/lib/routes/transaction.js @@ -1,25 +1,28 @@ -var express = require('express') -var router = express.Router() -var { estimate, broadcast } = require('./../controller/transaction') - -router.use(function timeLog(req, res, next) { - req.txRequest = req.body && req.body.payload - if (req.txRequest) { - console.log(`Transaction ${Date.now()} ${req.txRequest.messageType}`) - } else { - res.json({ error: 'No Request Found' }) - } - next() -}) - -router.use('/estimate', async function(req, res) { - const response = await estimate(req.txRequest) - res.json(response) -}) - -router.use('/broadcast', async function(req, res) { - const response = await broadcast(req.txRequest) - res.json(response) -}) - -module.exports = router +var express = require('express') +var router = express.Router() +var { estimate, broadcast } = require('./../controller/transaction') + +router.use(function timeLog(req, res, next) { + req.txRequest = req.body && req.body.payload + if (req.txRequest) { + console.log(`Transaction ${Date.now()} ${req.txRequest.messageType}`) + } else { + res.json({ error: 'No Request Found' }) + } + next() +}) + +router.use('/estimate', async function(req, res) { + const response = await estimate(req.txRequest) + res.json(response) +}) + +router.use('/broadcast', async function(req, res) { + const response = await broadcast( + req.txRequest, + req.headers.fingerprint || false + ) + res.json(response) +}) + +module.exports = router diff --git a/lib/schema.js b/lib/schema.js index 35e52040e9..aa148297e2 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -85,6 +85,16 @@ const typeDefs = gql` proposer_address: String } + type BlockV2 @cacheControl(maxAge: 10) { + networkId: String! + height: Int + hash: String + chainId: String + time: String + transactions: [TransactionV2] + proposer_address: String + } + type Maintenance { id: Int! message: String @@ -166,12 +176,13 @@ const typeDefs = gql` | SubmitProposalTx | VoteTx | DepositTx + | UnknownTx type TransactionV2 { type: String! hash: String! height: Int! - details: TransactionDetails + details: TransactionDetails! timestamp: String! memo: String fees: [Coin]! @@ -180,6 +191,7 @@ const typeDefs = gql` type SendTx { amount: Coin! + from: [String]! to: [String]! } @@ -197,6 +209,7 @@ const typeDefs = gql` type UnstakeTx { amount: Coin! from: [String]! + liquidDate: String } type ClaimRewardsTx { @@ -221,6 +234,10 @@ const typeDefs = gql` amount: Coin! } + type UnknownTx { + blockExplorerLink: String + } + type GovernanceParameters { depositDenom: String votingThreshold: Float @@ -270,6 +287,7 @@ const typeDefs = gql` type Query { block(networkId: String!, height: Int): Block + blockV2(networkId: String!, height: Int): BlockV2 proposal(networkId: String!, id: Int!): Proposal proposals(networkId: String!): [Proposal] validators( @@ -309,7 +327,11 @@ const typeDefs = gql` delegatorAddress: String! operatorAddress: String ): [Reward] - transactions(networkId: String!, address: String!): [Transaction] + transactions( + networkId: String! + address: String! + pageNumber: Int + ): [Transaction] transactionsV2(networkId: String!, address: String!): [TransactionV2] } ` diff --git a/lib/source/cosmosV0-source.js b/lib/source/cosmosV0-source.js index 2d9f0f8514..eb248f2a9c 100644 --- a/lib/source/cosmosV0-source.js +++ b/lib/source/cosmosV0-source.js @@ -81,7 +81,7 @@ class CosmosV0API extends RESTDataSource { return this.getRetry(url) } - async getStakinDenom() { + async getStakingDenom() { const stakingParameters = await this.query('/staking/parameters') return stakingParameters.bond_denom } @@ -125,6 +125,17 @@ class CosmosV0API extends RESTDataSource { : [] } + async getTransactionsV2ByHeight(height) { + const txs = await this.loadPaginatedTxs(`txs?tx.height=${height}`) + return Array.isArray(txs) + ? this.reducers.transactionsReducerV2( + txs, + this.reducers, + this.getStakingDenom() + ) + : [] + } + async getValidatorSigningInfos(validators) { const signingInfos = await Promise.all( validators.map(({ consensus_pubkey }) => @@ -337,6 +348,24 @@ class CosmosV0API extends RESTDataSource { return this.reducers.blockReducer(this.networkId, block, transactions) } + async getBlockByHeightV2(blockHeight) { + let block, transactions + if (blockHeight) { + const response = await Promise.all([ + this.getRetry(`blocks/${blockHeight}`), + this.getTransactionsV2ByHeight(blockHeight) + ]) + block = response[0] + transactions = response[1] + } else { + block = await this.getRetry(`blocks/latest`) + transactions = await this.getTransactionsV2ByHeight( + block.block_meta.header.height + ) + } + return this.reducers.blockReducer(this.networkId, block, transactions) + } + async getBalancesFromAddress(address) { this.checkAddress(address) const response = await this.query(`bank/balances/${address}`) @@ -476,7 +505,7 @@ class CosmosV0API extends RESTDataSource { delegatorAddress, validatorsDictionary ), - this.getStakinDenom() + this.getStakingDenom() ]) const rewards = await this.getRewards( delegatorAddress, diff --git a/lib/source/cosmosV2-source.js b/lib/source/cosmosV2-source.js index 958b9231cf..c2c3e7ef73 100644 --- a/lib/source/cosmosV2-source.js +++ b/lib/source/cosmosV2-source.js @@ -54,7 +54,7 @@ class CosmosV2API extends CosmosV0API { return pages[1] } - async getTransactions(address) { + async getTransactions(address, pageNumber = 0) { this.checkAddress(address) // getting page count @@ -64,8 +64,14 @@ class CosmosV2API extends CosmosV0API { ) const txs = await Promise.all([ - this.loadPaginatedTxs(`/txs?message.sender=${address}`, senderPage), - this.loadPaginatedTxs(`/txs?transfer.recipient=${address}`, recipientPage) + this.loadPaginatedTxs( + `/txs?message.sender=${address}`, + senderPage - pageNumber * 2 + ), + this.loadPaginatedTxs( + `/txs?transfer.recipient=${address}`, + recipientPage - pageNumber * 2 + ) ]).then(([senderTxs, recipientTxs]) => [].concat(senderTxs, recipientTxs)) return this.reducers.formatTransactionsReducer(txs, this.reducers) @@ -79,7 +85,8 @@ class CosmosV2API extends CosmosV0API { this.loadPaginatedTxs(`/txs?transfer.recipient=${address}`) ]).then(([senderTxs, recipientTxs]) => [].concat(senderTxs, recipientTxs)) - return this.reducers.transactionsReducerV2(txs, this.reducers) + const stakingDenom = await this.getStakingDenom() + return this.reducers.transactionsReducerV2(txs, this.reducers, stakingDenom) } extractInvolvedAddresses(transaction) { diff --git a/lib/source/emoneyV0-source.js b/lib/source/emoneyV0-source.js index 2d1bbc3994..190f2d23fb 100644 --- a/lib/source/emoneyV0-source.js +++ b/lib/source/emoneyV0-source.js @@ -95,7 +95,7 @@ class EMoneyV0API extends TerraV3API { selectedFiatCurrency, `EUR` ) - const fiatValue = eurValue / rates[selectedFiatCurrency] + const fiatValue = eurValue / rates[`EUR`] const currencySign = this.getCurrencySign(selectedFiatCurrency) return parseFloat(fiatValue) .toFixed(6) diff --git a/lib/statistics.js b/lib/statistics.js new file mode 100644 index 0000000000..9386ab8d14 --- /dev/null +++ b/lib/statistics.js @@ -0,0 +1,154 @@ +const database = require('./database') +const config = require('../config') + +let overviewedAddresses = {} +let prestoredTransactions = {} + +const clearOverviewedAddresses = () => { + // clear old records, that are older than 1 hour + Object.keys(overviewedAddresses).map(key => + process.hrtime(overviewedAddresses[key])[0] > 60 * 60 + ? delete overviewedAddresses[key] + : null + ) +} +const clearPrestoredTransactions = () => { + // clear old records, that are older than 10 minute + Object.keys(prestoredTransactions).filter(hash => + process.hrtime(prestoredTransactions[hash].time)[0] > 10 * 60 + ? delete prestoredTransactions[hash] + : null + ) +} + +const storePrestored = hash => { + let transaction = prestoredTransactions[hash] + if (transaction) { + new database(config)('').storeStatistics(transaction.payload) + delete prestoredTransactions[hash] + } + clearPrestoredTransactions() +} + +const filterPayload = payload => { + payload = Object.assign({}, payload) // copying object + // sending it to the db + let possibleKeys = [ + 'network', + 'address', + 'action', + 'value', + 'fingerprint', + 'denom' + ] // possible keys + Object.keys(payload).map(key => { + if (possibleKeys.indexOf(key) === -1) { + delete payload[key] + } + }) + return payload +} + +const store = async payload => { + payload = filterPayload(payload) + return new database(config)('').storeStatistics(payload) +} + +const prestore = async (payload, hash) => { + payload = filterPayload(payload) + prestoredTransactions[hash] = { + payload, + time: process.hrtime() + } +} +const defineActionValue = msg => { + if (msg.length == 1 && msg[0].value.amount) { + return msg[0].value.amount.amount + } + return 0 +} +const defineActionDenom = msg => { + if (msg.length == 1 && msg[0].value.amount) { + return msg[0].value.amount.denom + } + return '' +} +const defineActionType = type => { + if (type.indexOf('/MsgDelegate') !== -1) { + return 'Delegate' + } else if (type.indexOf('/MsgUndelegate') !== -1) { + return 'Undelegate' + } else if (type.indexOf('/MsgSend') !== -1) { + return 'Send' + } else if (type.indexOf('/MsgWithdrawDelegationReward') !== -1) { + return 'Withdraw' + } else if (type.indexOf('/MsgDeposit') !== -1) { + return 'Deposit' + } else if (type.indexOf('/MsgBeginRedelegate') !== -1) { + return 'Redelegate' + } + return type +} +const logOverview = (overview, fingerprint) => { + let key = overview.address + overview.networkId // just a key to store data about last request time + /* + we are requesting balances toooooo frequently + and we don't need so many records in db + so limiting writting posibilities to 1 hour + */ + if (overviewedAddresses[key]) { + if (process.hrtime(overviewedAddresses[key])[0] < 60 * 60) { + return clearOverviewedAddresses() + } + } + overviewedAddresses[key] = process.hrtime() // time in ms + // common object + let data = { + address: overview.address, + network: overview.networkId, + fingerprint, + action: ``, + value: `` + } + // store liquidStake + data.action = 'liquidStake' + data.value = overview.liquidStake.toString() + store(data) + // store totalStake + data.action = 'totalStake' + data.value = overview.totalStake.toString() + store(data) + // store totalRewards + data.action = 'totalRewards' + data.value = overview.totalRewards.toString() + store(data) + // store rewards + // summing rewards with one denom + if (overview.rewards) { + overview.rewards + .reduce((newArray, currentItem) => { + const index = newArray.findIndex(el => el.denom == currentItem.denom) + if (index !== -1) { + newArray[index].amount *= 1 + newArray[index].amount += currentItem.amount * 1 + } else { + newArray.push(currentItem) + } + return newArray + }, []) + .map(reward => { + data.action = 'rewards' + data.denom = reward.denom + data.value = reward.amount + store(data) + }) + } +} +module.exports = { + prestore, + storePrestored, + defineActionType, + logOverview, + defineActionDenom, + defineActionValue +}