From 171a81a45b6cb1408d0520fa1a2e660fabbebc34 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 20 Jun 2023 16:27:48 +0300 Subject: [PATCH] Store and display native coin market cap from the DB (#7585) * Store and display market cap from the DB * Fix reviwer comments * Fix reviewer comment * Fix reviewer comments * Process reviwer comments * Fix failing incompatible test * Fix test * Fix history_chart.js Return available_supply for backward compatibility --- CHANGELOG.md | 1 + .../assets/js/lib/history_chart.js | 31 ++--- .../channels/address_channel.ex | 7 +- .../account/api/v1/user_controller.ex | 7 +- .../address_coin_balance_controller.ex | 5 +- .../address_contract_controller.ex | 3 +- .../controllers/address_controller.ex | 7 +- .../address_decompiled_contract_controller.ex | 3 +- ...address_internal_transaction_controller.ex | 5 +- .../controllers/address_logs_controller.ex | 3 +- .../address_read_contract_controller.ex | 3 +- .../address_read_proxy_controller.ex | 3 +- .../controllers/address_token_controller.ex | 3 +- .../address_token_transfer_controller.ex | 5 +- .../address_transaction_controller.ex | 5 +- .../address_validation_controller.ex | 3 +- .../address_withdrawal_controller.ex | 5 +- .../address_write_contract_controller.ex | 3 +- .../address_write_proxy_controller.ex | 3 +- .../controllers/api/rpc/stats_controller.ex | 6 +- .../controllers/api/v2/address_controller.ex | 3 +- .../controllers/api/v2/stats_controller.ex | 21 +-- .../chain/market_history_chart_controller.ex | 31 +++-- .../controllers/chain_controller.ex | 3 +- .../controllers/transaction_controller.ex | 5 +- ...saction_internal_transaction_controller.ex | 3 +- .../controllers/transaction_log_controller.ex | 3 +- .../transaction_raw_trace_controller.ex | 3 +- .../transaction_state_controller.ex | 3 +- .../transaction_token_transfer_controller.ex | 3 +- .../lib/block_scout_web/notifier.ex | 5 +- .../views/account/watchlist_view.ex | 3 +- .../views/api/v2/address_view.ex | 3 +- .../views/api/v2/transaction_view.ex | 3 +- .../api/v2/stats_controller_test.exs | 1 - .../address_transaction_csv_exporter.ex | 3 +- .../explorer/chain/supply/exchange_rate.ex | 3 +- .../lib/explorer/exchange_rates/token.ex | 20 +-- .../lib/explorer/market/history/cataloger.ex | 120 +++++++++++++++--- .../market/history/source/market_cap.ex | 18 +++ .../history/source/market_cap/coin_gecko.ex | 60 +++++++++ .../history/{source.ex => source/price.ex} | 6 +- .../source/{ => price}/crypto_compare.ex | 12 +- apps/explorer/lib/explorer/market/market.ex | 42 ++++-- .../lib/explorer/market/market_history.ex | 5 +- ...30074105_market_history_add_market_cap.exs | 9 ++ .../market/history/cataloger_test.exs | 55 ++++++-- .../{ => price}/crypto_compare_test.exs | 12 +- apps/explorer/test/test_helper.exs | 2 +- 49 files changed, 384 insertions(+), 187 deletions(-) create mode 100644 apps/explorer/lib/explorer/market/history/source/market_cap.ex create mode 100644 apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex rename apps/explorer/lib/explorer/market/history/{source.ex => source/price.ex} (58%) rename apps/explorer/lib/explorer/market/history/source/{ => price}/crypto_compare.ex (86%) create mode 100644 apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs rename apps/explorer/test/explorer/market/history/source/{ => price}/crypto_compare_test.exs (90%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93c936cb11..84d19f6ba8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#7653](https://github.com/blockscout/blockscout/pull/7653) - Add support for DEPOSIT and WITHDRAW token transfer event in older contracts - [#7628](https://github.com/blockscout/blockscout/pull/7628) - Support partially verified property from verifier MS; Add property to track contracts automatically verified via eth-bytecode-db - [#7603](https://github.com/blockscout/blockscout/pull/7603) - Add Polygon Edge and optimism genesis files support +- [#7585](https://github.com/blockscout/blockscout/pull/7585) - Store and display native coin market cap from the DB - [#7513](https://github.com/blockscout/blockscout/pull/7513) - Add Polygon Edge support - [#7532](https://github.com/blockscout/blockscout/pull/7532) - Handle empty id in json rpc responses - [#7544](https://github.com/blockscout/blockscout/pull/7544) - Add ERC-1155 signatures to uncataloged_token_transfer_block_numbers diff --git a/apps/block_scout_web/assets/js/lib/history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js index b8f451d72c..d861602128 100644 --- a/apps/block_scout_web/assets/js/lib/history_chart.js +++ b/apps/block_scout_web/assets/js/lib/history_chart.js @@ -14,6 +14,7 @@ Chart.register(LineController, LineElement, PointElement, LinearScale, TimeScale // @ts-ignore const coinName = document.getElementById('js-coin-name').value +// @ts-ignore const chainId = document.getElementById('js-chain-id').value const priceDataKey = `priceData${coinName}` const txHistoryDataKey = `txHistoryData${coinName}${chainId}` @@ -188,15 +189,12 @@ function getTxHistoryData (transactionHistory) { return data } -function getMarketCapData (marketHistoryData, availableSupply) { +function getMarketCapData (marketHistoryData) { if (marketHistoryData.length === 0) { return getDataFromLocalStorage(marketCapDataKey) } - const data = marketHistoryData.map(({ date, closingPrice }) => { - const supply = (availableSupply !== null && typeof availableSupply === 'object') - ? availableSupply[date] - : availableSupply - return { x: date, y: closingPrice * supply } + const data = marketHistoryData.map(({ date, marketCap }) => { + return { x: date, y: marketCap } }) setDataToLocalStorage(marketCapDataKey, data) return data @@ -207,7 +205,7 @@ const priceLineColor = getPriceChartColor() const mcapLineColor = getMarketCapChartColor() class MarketHistoryChart { - constructor (el, availableSupply, _marketHistoryData, dataConfig) { + constructor (el, _marketHistoryData, dataConfig) { const axes = config.options.scales let priceActivated = true @@ -271,8 +269,6 @@ class MarketHistoryChart { axes.numTransactions.position = 'left' } - this.availableSupply = availableSupply - const txChartTitle = 'Daily transactions' const marketChartTitle = 'Daily price and market cap' let chartTitle = '' @@ -297,15 +293,9 @@ class MarketHistoryChart { this.chart = new Chart(el, config) } - updateMarketHistory (availableSupply, marketHistoryData) { + updateMarketHistory (marketHistoryData) { this.price.data = getPriceData(marketHistoryData) - if (this.availableSupply !== null && typeof this.availableSupply === 'object') { - const today = new Date().toJSON().slice(0, 10) - this.availableSupply[today] = availableSupply - this.marketCap.data = getMarketCapData(marketHistoryData, this.availableSupply) - } else { - this.marketCap.data = getMarketCapData(marketHistoryData, availableSupply) - } + this.marketCap.data = getMarketCapData(marketHistoryData) this.chart.update() } @@ -320,17 +310,16 @@ export function createMarketHistoryChart (el) { const dataConfig = $(el).data('history_chart_config') const $chartError = $('[data-chart-error-message]') - const chart = new MarketHistoryChart(el, 0, [], dataConfig) + const chart = new MarketHistoryChart(el, [], dataConfig) Object.keys(dataPaths).forEach(function (historySource) { $.getJSON(dataPaths[historySource], { type: 'JSON' }) .done(data => { switch (historySource) { case 'market': { - const availableSupply = JSON.parse(data.supply_data) - const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data)) + const marketHistoryData = humps.camelizeKeys(data.history_data) $(el).show() - chart.updateMarketHistory(availableSupply, marketHistoryData) + chart.updateMarketHistory(marketHistoryData) break } case 'transaction': { diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index 6072545f34..ba808cc16f 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -18,7 +18,6 @@ defmodule BlockScoutWeb.AddressChannel do alias Explorer.{Chain, Market, Repo} alias Explorer.Chain.{Hash, Transaction, Wei} alias Explorer.Chain.Hash.Address, as: AddressHash - alias Explorer.ExchangeRates.Token alias Phoenix.View intercept([ @@ -43,7 +42,7 @@ defmodule BlockScoutWeb.AddressChannel do with {:ok, casted_address_hash} <- AddressHash.cast(socket.assigns.address_hash), {:ok, address = %{fetched_coin_balance: balance}} when not is_nil(balance) <- Chain.hash_to_address(casted_address_hash), - exchange_rate <- Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate <- Market.get_coin_exchange_rate(), {:ok, rendered} <- render_balance_card(address, exchange_rate, socket) do reply = {:ok, @@ -233,7 +232,7 @@ defmodule BlockScoutWeb.AddressChannel do ) do push(socket, "current_coin_balance", %{ coin_balance: (coin_balance && coin_balance.value) || %Wei{value: Decimal.new(0)}, - exchange_rate: (Market.get_exchange_rate(Explorer.coin()) || Token.null()).usd_value, + exchange_rate: Market.get_coin_exchange_rate().usd_value, block_number: block_number }) end @@ -248,7 +247,7 @@ defmodule BlockScoutWeb.AddressChannel do conn: socket, address: Chain.hash_to_address(hash), coin_balance: (coin_balance && coin_balance.value) || %Wei{value: Decimal.new(0)}, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_coin_exchange_rate() ) rendered_link = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex index 42b67c7c17..6c7f54e867 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/account/api/v1/user_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do alias Explorer.Account.Api.Key, as: ApiKey alias Explorer.Account.CustomABI alias Explorer.Account.{Identity, PublicTagsRequest, TagAddress, TagTransaction, WatchlistAddress} - alias Explorer.ExchangeRates.Token alias Explorer.{Market, Repo} alias Plug.CSRFProtection @@ -34,7 +33,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do conn |> put_status(200) |> render(:watchlist_addresses, %{ - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), watchlist_addresses: watchlist_with_addresses.watchlist_addresses }) end @@ -103,7 +102,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do |> put_status(200) |> render(:watchlist_address, %{ watchlist_address: watchlist_address, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_coin_exchange_rate() }) end end @@ -160,7 +159,7 @@ defmodule BlockScoutWeb.Account.Api.V1.UserController do |> put_status(200) |> render(:watchlist_address, %{ watchlist_address: watchlist_address, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_coin_exchange_rate() }) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex index 36c4eed106..c0bbf00975 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex @@ -12,7 +12,6 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do alias BlockScoutWeb.{AccessHelper, AddressCoinBalanceView, Controller} alias Explorer.{Chain, Market} alias Explorer.Chain.{Address, Wei} - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -76,7 +75,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do render(conn, "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), current_path: Controller.current_full_path(conn), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), tags: get_address_tags(address_hash, current_user(conn)) @@ -102,7 +101,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do "index.html", address: address, coin_balance_status: nil, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), current_path: Controller.current_full_path(conn), tags: get_address_tags(address_hash, current_user(conn)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex index da6e864e69..d049ce1511 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex @@ -7,7 +7,6 @@ defmodule BlockScoutWeb.AddressContractController do alias BlockScoutWeb.AccessHelper alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Explorer.SmartContract.Solidity.PublishHelper alias Indexer.Fetcher.CoinBalanceOnDemand @@ -31,7 +30,7 @@ defmodule BlockScoutWeb.AddressContractController do "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), tags: get_address_tags(address_hash, current_user(conn)) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex index 4331bdf6be..9e37382e12 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -16,7 +16,6 @@ defmodule BlockScoutWeb.AddressController do alias Explorer.{Chain, Market} alias Explorer.Chain.Wei - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -41,7 +40,7 @@ defmodule BlockScoutWeb.AddressController do ) end - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() total_supply = Chain.total_supply() items_count_str = Map.get(params, "items_count") @@ -101,7 +100,7 @@ defmodule BlockScoutWeb.AddressController do "_show_address_transactions.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), current_path: Controller.current_full_path(conn), @@ -131,7 +130,7 @@ defmodule BlockScoutWeb.AddressController do "_show_address_transactions.html", address: address, coin_balance_status: nil, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), current_path: Controller.current_full_path(conn), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex index 292ea0f929..69c2d7dc0f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex @@ -6,7 +6,6 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do alias BlockScoutWeb.AccessHelper alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand def index(conn, %{"address_id" => address_hash_string} = params) do @@ -18,7 +17,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), tags: get_address_tags(address_hash, current_user(conn)) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex index daca356e1f..92fbe1712b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex @@ -12,7 +12,6 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView} alias Explorer.{Chain, Market} alias Explorer.Chain.{Address, Wei} - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -86,7 +85,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), current_path: Controller.current_full_path(conn), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), tags: get_address_tags(address_hash, current_user(conn)) @@ -113,7 +112,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do address: address, filter: params["filter"], coin_balance_status: nil, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), current_path: Controller.current_full_path(conn), tags: get_address_tags(address_hash, current_user(conn)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex index 67bbfc5958..ea6c656bac 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex @@ -11,7 +11,6 @@ defmodule BlockScoutWeb.AddressLogsController do alias BlockScoutWeb.{AccessHelper, AddressLogsView, Controller} alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -67,7 +66,7 @@ defmodule BlockScoutWeb.AddressLogsController do address: address, current_path: Controller.current_full_path(conn), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), tags: get_address_tags(address_hash, current_user(conn)) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex index b963cfa3e4..ae534280cc 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex @@ -15,7 +15,6 @@ defmodule BlockScoutWeb.AddressReadContractController do alias BlockScoutWeb.AddressView alias Explorer.{Chain, Market} alias Explorer.Chain.Address - alias Explorer.ExchangeRates.Token alias Explorer.SmartContract.Reader alias Indexer.Fetcher.CoinBalanceOnDemand @@ -40,7 +39,7 @@ defmodule BlockScoutWeb.AddressReadContractController do type: :regular, action: :read, custom_abi: custom_abi?, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_coin_exchange_rate() ] with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex index 6e4534af7c..5a4d9c981d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.AddressReadProxyController do alias BlockScoutWeb.AccessHelper alias Explorer.{Chain, Market} alias Explorer.Chain.Address - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand def index(conn, %{"address_id" => address_hash_string} = params) do @@ -33,7 +32,7 @@ defmodule BlockScoutWeb.AddressReadProxyController do type: :proxy, action: :read, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), tags: get_address_tags(address_hash, current_user(conn)) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex index f896e75e3f..8762432af7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.AddressTokenController do alias BlockScoutWeb.{AccessHelper, AddressTokenView, Controller} alias Explorer.{Chain, Market} alias Explorer.Chain.Address - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -73,7 +72,7 @@ defmodule BlockScoutWeb.AddressTokenController do address: address, current_path: Controller.current_full_path(conn), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), tags: get_address_tags(address_hash, current_user(conn)) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex index 8bb51818f6..e8d21d0d2c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -5,7 +5,6 @@ defmodule BlockScoutWeb.AddressTokenTransferController do import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] alias BlockScoutWeb.{AccessHelper, Controller, TransactionView} - alias Explorer.ExchangeRates.Token alias Explorer.{Chain, Market} alias Explorer.Chain.Address alias Indexer.Fetcher.CoinBalanceOnDemand @@ -109,7 +108,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], current_path: Controller.current_full_path(conn), token: token, @@ -202,7 +201,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], current_path: Controller.current_full_path(conn), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 999c619bc9..fc9aad5e20 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -23,7 +23,6 @@ defmodule BlockScoutWeb.AddressTransactionController do alias Explorer.Chain.Wei - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -124,7 +123,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), current_path: Controller.current_full_path(conn), @@ -154,7 +153,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "index.html", address: address, coin_balance_status: nil, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), filter: params["filter"], counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), current_path: Controller.current_full_path(conn), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex index a4893bf3ce..244deb3af0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex @@ -12,7 +12,6 @@ defmodule BlockScoutWeb.AddressValidationController do import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] alias BlockScoutWeb.{AccessHelper, BlockView, Controller} - alias Explorer.ExchangeRates.Token alias Explorer.{Chain, Market} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -83,7 +82,7 @@ defmodule BlockScoutWeb.AddressValidationController do coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), current_path: Controller.current_full_path(conn), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), tags: get_address_tags(address_hash, current_user(conn)) ) else diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex index 7fcb05d5b4..9378996174 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_withdrawal_controller.ex @@ -16,7 +16,6 @@ defmodule BlockScoutWeb.AddressWithdrawalController do alias Explorer.Chain.Wei - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -78,7 +77,7 @@ defmodule BlockScoutWeb.AddressWithdrawalController do "index.html", address: address, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), current_path: Controller.current_full_path(conn), tags: get_address_tags(address_hash, current_user(conn)) @@ -107,7 +106,7 @@ defmodule BlockScoutWeb.AddressWithdrawalController do "index.html", address: address, coin_balance_status: nil, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => address_hash_string}), current_path: Controller.current_full_path(conn), tags: get_address_tags(address_hash, current_user(conn)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex index cd88e97a0b..32fc477ed2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex @@ -15,7 +15,6 @@ defmodule BlockScoutWeb.AddressWriteContractController do alias BlockScoutWeb.AddressView alias Explorer.{Chain, Market} alias Explorer.Chain.Address - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand def index(conn, %{"address_id" => address_hash_string} = params) do @@ -35,7 +34,7 @@ defmodule BlockScoutWeb.AddressWriteContractController do type: :regular, action: :write, custom_abi: custom_abi?, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_coin_exchange_rate() ] with false <- AddressView.contract_interaction_disabled?(), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex index 844c5d3ddc..68903261d6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.AddressWriteProxyController do alias BlockScoutWeb.{AccessHelper, AddressView} alias Explorer.{Chain, Market} alias Explorer.Chain.Address - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand def index(conn, %{"address_id" => address_hash_string} = params) do @@ -34,7 +33,7 @@ defmodule BlockScoutWeb.AddressWriteProxyController do type: :proxy, action: :write, coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)}), tags: get_address_tags(address_hash, current_user(conn)) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex index e8f829bbec..19fcf8768c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex @@ -3,8 +3,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do use Explorer.Schema - alias Explorer - alias Explorer.{Chain, Etherscan, ExchangeRates} + alias Explorer.{Chain, Etherscan, Market} alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt} alias Explorer.Chain.Wei @@ -61,8 +60,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do end def coinprice(conn, _params) do - symbol = Explorer.coin() - rates = ExchangeRates.lookup(symbol) + rates = Market.get_coin_exchange_rate() render(conn, "coinprice.json", rates: rates) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index d396d27e02..a2695b155e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -15,7 +15,6 @@ defmodule BlockScoutWeb.API.V2.AddressController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} - alias Explorer.ExchangeRates.Token alias Explorer.{Chain, Market} alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @@ -401,7 +400,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do next_page_params = next_page_params(next_page, addresses, params) - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() total_supply = Chain.total_supply() conn diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index d6974c090c..6ffbd0c6bb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do use Phoenix.Controller alias BlockScoutWeb.API.V2.Helper + alias BlockScoutWeb.Chain.MarketHistoryChartController alias Explorer.{Chain, Market} alias Explorer.Chain.Cache.Block, as: BlockCache alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage} @@ -9,7 +10,6 @@ defmodule BlockScoutWeb.API.V2.StatsController do alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime - alias Explorer.ExchangeRates.Token alias Timex.Duration @api_true [api?: true] @@ -24,7 +24,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do :standard end - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() transaction_stats = Helper.get_transaction_stats() @@ -46,7 +46,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do "total_addresses" => @api_true |> Chain.address_estimated_count() |> to_string(), "total_transactions" => TransactionCache.estimated_count() |> to_string(), "average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(), - "coin_price" => exchange_rate.usd_value || Market.get_native_coin_exchange_rate_from_db(), + "coin_price" => exchange_rate.usd_value, "total_gas_used" => GasUsage.total() |> to_string(), "transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(), "gas_used_today" => Enum.at(transaction_stats, 0).gas_used, @@ -91,18 +91,19 @@ defmodule BlockScoutWeb.API.V2.StatsController do end def market_chart(conn, _params) do - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() recent_market_history = Market.fetch_recent_history() + current_total_supply = available_supply(Chain.supply_for_days(), exchange_rate) - market_history_data = + price_history_data = recent_market_history |> case do [today | the_rest] -> [ %{ today - | closing_price: if(exchange_rate.usd_value, do: exchange_rate.usd_value, else: today.closing_price) + | closing_price: exchange_rate.usd_value } | the_rest ] @@ -110,11 +111,15 @@ defmodule BlockScoutWeb.API.V2.StatsController do data -> data end - |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) + |> Enum.map(fn day -> Map.take(day, [:closing_price, :market_cap, :date]) end) + + market_history_data = + MarketHistoryChartController.encode_market_history_data(price_history_data, current_total_supply) json(conn, %{ chart_data: market_history_data, - available_supply: available_supply(Chain.supply_for_days(), exchange_rate) + # todo: remove when new frontend is ready to use data from chart_data property only + available_supply: current_total_supply }) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex index 238f858769..5ab997f4a9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex @@ -2,26 +2,27 @@ defmodule BlockScoutWeb.Chain.MarketHistoryChartController do use BlockScoutWeb, :controller alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token def show(conn, _params) do if ajax?(conn) do - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() recent_market_history = Market.fetch_recent_history() + current_total_supply = available_supply(Chain.supply_for_days(), exchange_rate) - market_history_data = + price_history_data = case recent_market_history do [today | the_rest] -> - encode_market_history_data([%{today | closing_price: exchange_rate.usd_value} | the_rest]) + [%{today | closing_price: exchange_rate.usd_value} | the_rest] data -> - encode_market_history_data(data) + data end + market_history_data = encode_market_history_data(price_history_data, current_total_supply) + json(conn, %{ - history_data: market_history_data, - supply_data: available_supply(Chain.supply_for_days(), exchange_rate) + history_data: market_history_data }) else unprocessable_entity(conn) @@ -41,12 +42,22 @@ defmodule BlockScoutWeb.Chain.MarketHistoryChartController do end end - defp encode_market_history_data(market_history_data) do + def encode_market_history_data(market_history_data, current_total_supply) when is_binary(current_total_supply) do + encode_market_history_data(market_history_data, Decimal.new(current_total_supply)) + end + + def encode_market_history_data(market_history_data, current_total_supply) do market_history_data - |> Enum.map(fn day -> Map.take(day, [:closing_price, :date]) end) + |> Enum.map(fn day -> + market_cap = if day.market_cap, do: day.market_cap, else: Decimal.mult(current_total_supply, day.closing_price) + + day + |> Map.put(:market_cap, market_cap) + |> Map.take([:closing_price, :market_cap, :date]) + end) |> Jason.encode() |> case do - {:ok, data} -> data + {:ok, data} -> Jason.decode!(data) _ -> [] end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 95cd87fcf7..afa8143329 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -12,7 +12,6 @@ defmodule BlockScoutWeb.ChainController do alias Explorer.Chain.Cache.Transaction, as: TransactionCache alias Explorer.Chain.Supply.RSK alias Explorer.Counters.AverageBlockTime - alias Explorer.ExchangeRates.Token alias Explorer.Market alias Phoenix.View @@ -31,7 +30,7 @@ defmodule BlockScoutWeb.ChainController do :standard end - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() transaction_stats = Helper.get_transaction_stats() diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex index 9158d1ca85..5983ab8920 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -25,7 +25,6 @@ defmodule BlockScoutWeb.TransactionController do alias Explorer.{Chain, Market} alias Explorer.Chain.Cache.Transaction, as: TransactionCache - alias Explorer.ExchangeRates.Token alias Phoenix.View @necessity_by_association %{ @@ -161,7 +160,7 @@ defmodule BlockScoutWeb.TransactionController do render( conn, "show_token_transfers.html", - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), block_height: Chain.block_height(), current_path: Controller.current_full_path(conn), current_user: current_user(conn), @@ -199,7 +198,7 @@ defmodule BlockScoutWeb.TransactionController do render( conn, "show_internal_transactions.html", - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), current_path: Controller.current_full_path(conn), current_user: current_user(conn), block_height: Chain.block_height(), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 9f2b6c8fac..4c6a017e77 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView, TransactionController} alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Phoenix.View def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do @@ -102,7 +101,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do render( conn, "index.html", - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), current_path: Controller.current_full_path(conn), current_user: current_user(conn), block_height: Chain.block_height(), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex index 70cb686bad..53ba82ed35 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.TransactionLogController do alias BlockScoutWeb.{AccessHelper, Controller, TransactionController, TransactionLogView} alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Phoenix.View def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do @@ -99,7 +98,7 @@ defmodule BlockScoutWeb.TransactionLogController do current_path: Controller.current_full_path(conn), current_user: current_user(conn), transaction: transaction, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), from_tags: get_address_tags(transaction.from_address_hash, current_user(conn)), to_tags: get_address_tags(transaction.to_address_hash, current_user(conn)), tx_tags: diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex index 0fa47a09f2..93002d4348 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.TransactionRawTraceController do alias BlockScoutWeb.{AccessHelper, TransactionController} alias EthereumJSONRPC alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.FirstTraceOnDemand def index(conn, %{"transaction_id" => hash_string} = params) do @@ -59,7 +58,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do render( conn, "index.html", - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), internal_transactions: internal_transactions, block_height: Chain.block_height(), current_user: current_user(conn), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex index 37a9a24dbf..e2020a47bc 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_state_controller.ex @@ -10,7 +10,6 @@ defmodule BlockScoutWeb.TransactionStateController do } alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Phoenix.View import BlockScoutWeb.Account.AuthController, only: [current_user: 1] @@ -107,7 +106,7 @@ defmodule BlockScoutWeb.TransactionStateController do render( conn, "index.html", - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), block_height: Chain.block_height(), current_path: Controller.current_full_path(conn), show_token_transfers: Chain.transaction_has_token_transfers?(transaction_hash), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex index e2fdffb4ed..d0f7fed158 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do alias BlockScoutWeb.{AccessHelper, Controller, TransactionController, TransactionTokenTransferView} alias Explorer.{Chain, Market} - alias Explorer.ExchangeRates.Token alias Phoenix.View {:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000") @@ -105,7 +104,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do render( conn, "index.html", - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), + exchange_rate: Market.get_coin_exchange_rate(), block_height: Chain.block_height(), current_path: Controller.current_full_path(conn), current_user: current_user(conn), diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index 7ca4a892a8..be32348540 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -21,7 +21,6 @@ defmodule BlockScoutWeb.Notifier do alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.{AverageBlockTime, Helper} - alias Explorer.ExchangeRates.Token alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} alias Phoenix.View @@ -115,7 +114,7 @@ defmodule BlockScoutWeb.Notifier do end def handle_event({:chain_event, :exchange_rate}) do - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() market_history_data = case Market.fetch_recent_history() do @@ -323,7 +322,7 @@ defmodule BlockScoutWeb.Notifier do "balance_update", %{ address: address, - exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate: Market.get_coin_exchange_rate() } ) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex index fa39663236..c6d513a2bf 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/account/watchlist_view.ex @@ -3,7 +3,6 @@ defmodule BlockScoutWeb.Account.WatchlistView do alias BlockScoutWeb.Account.WatchlistAddressView alias Explorer.Account.WatchlistAddress - alias Explorer.ExchangeRates.Token alias Explorer.Market alias Indexer.Fetcher.CoinBalanceOnDemand @@ -12,6 +11,6 @@ defmodule BlockScoutWeb.Account.WatchlistView do end def exchange_rate do - Market.get_exchange_rate(Explorer.coin()) || Token.null() + Market.get_coin_exchange_rate() end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index 7abccd0650..379959a256 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -8,7 +8,6 @@ defmodule BlockScoutWeb.API.V2.AddressView do alias BlockScoutWeb.API.V2.Helper alias Explorer.{Chain, Market} alias Explorer.Chain.{Address, SmartContract} - alias Explorer.ExchangeRates.Token @api_true [api?: true] @@ -78,7 +77,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do end balance = address.fetched_coin_balance && address.fetched_coin_balance.value - exchange_rate = (Market.get_exchange_rate(Explorer.coin()) || Token.null()).usd_value + exchange_rate = Market.get_coin_exchange_rate().usd_value creator_hash = AddressView.from_address_hash(address) creation_tx = creator_hash && AddressView.transaction_hash(address) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index 8368ab1cf5..9c1eee2bc0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -7,7 +7,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do alias BlockScoutWeb.Tokens.Helper, as: TokensHelper alias BlockScoutWeb.TransactionStateView alias Ecto.Association.NotLoaded - alias Explorer.ExchangeRates.Token, as: TokenRate alias Explorer.{Chain, Market} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Token, Transaction, Wei} alias Explorer.Chain.Block.Reward @@ -399,7 +398,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "token_transfers" => token_transfers(transaction.token_transfers, conn, single_tx?), "token_transfers_overflow" => token_transfers_overflow(transaction.token_transfers, single_tx?), "actions" => transaction_actions(transaction.transaction_actions), - "exchange_rate" => (Market.get_exchange_rate(Explorer.coin()) || TokenRate.null()).usd_value, + "exchange_rate" => Market.get_coin_exchange_rate().usd_value, "method" => method_name(transaction, decoded_input), "tx_types" => tx_types(transaction), "tx_tag" => GetTransactionTags.get_transaction_tags(transaction.hash, current_user(single_tx? && conn)), diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs index 1c1659791f..85ac41e1d1 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/stats_controller_test.exs @@ -42,7 +42,6 @@ defmodule BlockScoutWeb.API.V2.StatsControllerTest do assert response = json_response(request, 200) assert response["chart_data"] == [] - assert response["available_supply"] == 0 end end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index b53d1046e9..ae47768cff 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -12,7 +12,6 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do alias Explorer.Market.MarketHistory alias Explorer.Chain.{Address, Transaction, Wei} alias Explorer.Chain.CSVExport.Helper - alias Explorer.ExchangeRates.Token @necessity_by_association [ necessity_by_association: %{ @@ -32,7 +31,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do @spec export(Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() def export(address, from_period, to_period, filter_type \\ nil, filter_value \\ nil) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + exchange_rate = Market.get_coin_exchange_rate() address.hash |> fetch_all_transactions(from_block, to_block, filter_type, filter_value, @paging_options) diff --git a/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex b/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex index d45a8edc02..1ef83b1a90 100644 --- a/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex +++ b/apps/explorer/lib/explorer/chain/supply/exchange_rate.ex @@ -5,7 +5,6 @@ defmodule Explorer.Chain.Supply.ExchangeRate do use Explorer.Chain.Supply - alias Explorer.ExchangeRates.Token alias Explorer.Market def circulating do @@ -17,6 +16,6 @@ defmodule Explorer.Chain.Supply.ExchangeRate do end def exchange_rate do - Market.get_exchange_rate(Explorer.coin()) || Token.null() + Market.get_coin_exchange_rate() end end diff --git a/apps/explorer/lib/explorer/exchange_rates/token.ex b/apps/explorer/lib/explorer/exchange_rates/token.ex index 8aa6d3b721..1e26d3267e 100644 --- a/apps/explorer/lib/explorer/exchange_rates/token.ex +++ b/apps/explorer/lib/explorer/exchange_rates/token.ex @@ -18,16 +18,16 @@ defmodule Explorer.ExchangeRates.Token do * `:volume_24h_usd` - The volume from the last 24 hours in USD """ @type t :: %__MODULE__{ - available_supply: Decimal.t(), - total_supply: Decimal.t(), - btc_value: Decimal.t(), - id: String.t(), - last_updated: DateTime.t(), - market_cap_usd: Decimal.t(), - name: String.t(), - symbol: String.t(), - usd_value: Decimal.t(), - volume_24h_usd: Decimal.t() + available_supply: Decimal.t() | nil, + total_supply: Decimal.t() | nil, + btc_value: Decimal.t() | nil, + id: String.t() | nil, + last_updated: DateTime.t() | nil, + market_cap_usd: Decimal.t() | nil, + name: String.t() | nil, + symbol: String.t() | nil, + usd_value: Decimal.t() | nil, + volume_24h_usd: Decimal.t() | nil } @derive Jason.Encoder diff --git a/apps/explorer/lib/explorer/market/history/cataloger.ex b/apps/explorer/lib/explorer/market/history/cataloger.ex index 8545ffa159..0672412d33 100644 --- a/apps/explorer/lib/explorer/market/history/cataloger.ex +++ b/apps/explorer/lib/explorer/market/history/cataloger.ex @@ -28,38 +28,68 @@ defmodule Explorer.Market.History.Cataloger do @typep milliseconds :: non_neg_integer() + @price_failed_attempts 10 + @market_cap_failed_attempts 3 + @impl GenServer def init(:ok) do - send(self(), {:fetch_history, 365}) + send(self(), {:fetch_price_history, 365}) {:ok, %{}} end @impl GenServer - def handle_info({:fetch_history, day_count}, state) do - fetch_history(day_count) + def handle_info({:fetch_price_history, day_count}, state) do + fetch_price_history(day_count) + + {:noreply, state} + end + + @impl GenServer + def handle_info(:fetch_market_cap_history, state) do + fetch_market_cap_history() {:noreply, state} end @impl GenServer # Record fetch successful. - def handle_info({_ref, {_, _, {:ok, records}}}, state) do - Market.bulk_insert_history(records) + def handle_info({_ref, {:price_history, {_, _, {:ok, records}}}}, state) do + Process.send(self(), :fetch_market_cap_history, []) + state = state |> Map.put_new(:price_records, records) - # Schedule next check for history - fetch_after = config_or_default(:history_fetch_interval, :timer.minutes(60)) - Process.send_after(self(), {:fetch_history, 1}, fetch_after) + {:noreply, state |> Map.put_new(:price_records, state)} + end + + @impl GenServer + # Record fetch successful. + def handle_info({_ref, {:market_cap_history, {_, {:ok, nil}}}}, state) do + market_cap_history(state.price_records, state) + end + + @impl GenServer + # Record fetch successful. + def handle_info({_ref, {:market_cap_history, {_, {:ok, market_cap_record}}}}, state) do + records = compile_records(state.price_records, market_cap_record) + market_cap_history(records, state) + end + + # Failed to get records. Try again. + @impl GenServer + def handle_info({_ref, {:price_history, {day_count, failed_attempts, :error}}}, state) do + Logger.warn(fn -> "Failed to fetch price history. Trying again." end) + + fetch_price_history(day_count, failed_attempts + 1) {:noreply, state} end # Failed to get records. Try again. @impl GenServer - def handle_info({_ref, {day_count, failed_attempts, :error}}, state) do - Logger.warn(fn -> "Failed to fetch market history. Trying again." end) + def handle_info({_ref, {:market_cap_history, {failed_attempts, :error}}}, state) do + Logger.warn(fn -> "Failed to fetch market cap history. Trying again." end) - fetch_history(day_count, failed_attempts + 1) + fetch_market_cap_history(failed_attempts + 1) {:noreply, state} end @@ -78,6 +108,16 @@ defmodule Explorer.Market.History.Cataloger do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end + defp market_cap_history(records, state) do + Market.bulk_insert_history(records) + + # Schedule next check for history + fetch_after = config_or_default(:history_fetch_interval, :timer.minutes(60)) + Process.send_after(self(), {:fetch_price_history, 1}, fetch_after) + + {:noreply, state} + end + @spec base_backoff :: milliseconds() defp base_backoff do config_or_default(:base_backoff, 100) @@ -88,19 +128,65 @@ defmodule Explorer.Market.History.Cataloger do Application.get_env(:explorer, __MODULE__, [])[key] || default end - @spec source() :: module() - defp source do - config_or_default(:source, Explorer.Market.History.Source.CryptoCompare) + @spec source_price() :: module() + defp source_price do + config_or_default(:source, Explorer.Market.History.Source.Price.CryptoCompare) + end + + @spec source_market_cap() :: module() + defp source_market_cap do + config_or_default(:source_market_cap, Explorer.Market.History.Source.MarketCap.CoinGecko) + end + + @spec fetch_price_history(non_neg_integer(), non_neg_integer()) :: Task.t() + defp fetch_price_history(day_count, failed_attempts \\ 0) do + Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn -> + Process.sleep(delay(failed_attempts)) + + if failed_attempts < @price_failed_attempts do + {:price_history, {day_count, failed_attempts, source_price().fetch_price_history(day_count)}} + else + {:price_history, {day_count, failed_attempts, {:ok, []}}} + end + end) end - @spec fetch_history(non_neg_integer(), non_neg_integer()) :: Task.t() - defp fetch_history(day_count, failed_attempts \\ 0) do + @spec fetch_market_cap_history(non_neg_integer()) :: Task.t() + defp fetch_market_cap_history(failed_attempts \\ 0) do Task.Supervisor.async_nolink(Explorer.MarketTaskSupervisor, fn -> Process.sleep(delay(failed_attempts)) - {day_count, failed_attempts, source().fetch_history(day_count)} + + if failed_attempts < @market_cap_failed_attempts do + {:market_cap_history, {failed_attempts, source_market_cap().fetch_market_cap()}} + else + {:market_cap_history, {failed_attempts, {:ok, nil}}} + end end) end + defp compile_records(price_records, market_cap_record) do + if market_cap_record do + if Enum.empty?(price_records) do + [market_cap_record] + else + today_index = + Enum.find_index(price_records, fn price -> + price.date == market_cap_record.date + end) + + today = + price_records + |> Enum.at(today_index) + |> Map.put(:market_cap, market_cap_record.market_cap) + + price_records + |> List.replace_at(today_index, today) + end + else + price_records + end + end + @spec delay(non_neg_integer()) :: milliseconds() defp delay(0), do: 0 defp delay(1), do: base_backoff() diff --git a/apps/explorer/lib/explorer/market/history/source/market_cap.ex b/apps/explorer/lib/explorer/market/history/source/market_cap.ex new file mode 100644 index 0000000000..f79e2d12c0 --- /dev/null +++ b/apps/explorer/lib/explorer/market/history/source/market_cap.ex @@ -0,0 +1,18 @@ +defmodule Explorer.Market.History.Source.MarketCap do + @moduledoc """ + Interface for a source that allows for fetching of market cap history. + """ + + @typedoc """ + Record of market values for a specific date. + """ + @type record :: %{ + date: Date.t(), + market_cap: Decimal.t() + } + + @doc """ + Fetch history for a specified amount of days in the past. + """ + @callback fetch_market_cap() :: {:ok, [record()]} | :error +end diff --git a/apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex b/apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex new file mode 100644 index 0000000000..9875f0d0a6 --- /dev/null +++ b/apps/explorer/lib/explorer/market/history/source/market_cap/coin_gecko.ex @@ -0,0 +1,60 @@ +defmodule Explorer.Market.History.Source.MarketCap.CoinGecko do + @moduledoc """ + Adapter for fetching current market from CoinGecko. + + The current market is fetched for the configured coin. You can specify a + different coin by changing the targeted coin. + + # In config.exs + config :explorer, coin: "POA" + + """ + + alias Explorer.ExchangeRates.Source + alias Explorer.ExchangeRates.Source.CoinGecko, as: ExchangeRatesSourceCoinGecko + alias Explorer.Market.History.Source.MarketCap, as: SourceMarketCap + + @behaviour SourceMarketCap + + @impl SourceMarketCap + def fetch_market_cap do + url = ExchangeRatesSourceCoinGecko.source_url() + + if url do + case Source.http_request(url, ExchangeRatesSourceCoinGecko.headers()) do + {:ok, data} -> + result = + data + |> format_data() + + {:ok, result} + + _ -> + :error + end + else + :error + end + end + + @spec date(String.t()) :: Date.t() + defp date(date_time_string) do + with {:ok, datetime, _} <- DateTime.from_iso8601(date_time_string) do + datetime + |> DateTime.to_date() + end + end + + @spec format_data(term()) :: SourceMarketCap.record() | nil + defp format_data(nil), do: nil + + defp format_data(data) do + market_data = data["market_data"] + market_cap = market_data["market_cap"] + + %{ + market_cap: Decimal.new(to_string(market_cap["usd"])), + date: date(data["last_updated"]) + } + end +end diff --git a/apps/explorer/lib/explorer/market/history/source.ex b/apps/explorer/lib/explorer/market/history/source/price.ex similarity index 58% rename from apps/explorer/lib/explorer/market/history/source.ex rename to apps/explorer/lib/explorer/market/history/source/price.ex index af1c96e4ba..07924c1fca 100644 --- a/apps/explorer/lib/explorer/market/history/source.ex +++ b/apps/explorer/lib/explorer/market/history/source/price.ex @@ -1,6 +1,6 @@ -defmodule Explorer.Market.History.Source do +defmodule Explorer.Market.History.Source.Price do @moduledoc """ - Interface for a source that allows for fetching of market history. + Interface for a source that allows for fetching of coin price. """ @typedoc """ @@ -15,5 +15,5 @@ defmodule Explorer.Market.History.Source do @doc """ Fetch history for a specified amount of days in the past. """ - @callback fetch_history(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error + @callback fetch_price_history(previous_days :: non_neg_integer()) :: {:ok, [record()]} | :error end diff --git a/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex b/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex similarity index 86% rename from apps/explorer/lib/explorer/market/history/source/crypto_compare.ex rename to apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex index 6888d83b2b..76693d3706 100644 --- a/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex +++ b/apps/explorer/lib/explorer/market/history/source/price/crypto_compare.ex @@ -1,4 +1,4 @@ -defmodule Explorer.Market.History.Source.CryptoCompare do +defmodule Explorer.Market.History.Source.Price.CryptoCompare do @moduledoc """ Adapter for fetching market history from https://cryptocompare.com. @@ -10,15 +10,15 @@ defmodule Explorer.Market.History.Source.CryptoCompare do """ - alias Explorer.Market.History.Source + alias Explorer.Market.History.Source.Price, as: SourcePrice alias HTTPoison.Response - @behaviour Source + @behaviour SourcePrice @typep unix_timestamp :: non_neg_integer() - @impl Source - def fetch_history(previous_days) do + @impl SourcePrice + def fetch_price_history(previous_days) do url = history_url(previous_days) headers = [{"Content-Type", "application/json"}] @@ -49,7 +49,7 @@ defmodule Explorer.Market.History.Source.CryptoCompare do |> DateTime.to_date() end - @spec format_data(String.t()) :: [Source.record()] + @spec format_data(String.t()) :: [SourcePrice.record()] defp format_data(data) do json = Jason.decode!(data) diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 466c6c5617..083f39403c 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -7,14 +7,6 @@ defmodule Explorer.Market do alias Explorer.Market.{MarketHistory, MarketHistoryCache} alias Explorer.{ExchangeRates, Repo} - @doc """ - Get most recent exchange rate for the given symbol. - """ - @spec get_exchange_rate(String.t()) :: Token.t() | nil - def get_exchange_rate(symbol) do - ExchangeRates.lookup(symbol) - end - @doc """ Retrieves the history for the recent specified amount of days. @@ -28,7 +20,7 @@ defmodule Explorer.Market do @doc """ Retrieves today's native coin exchange rate from the database. """ - @spec get_native_coin_exchange_rate_from_db() :: Token.t() | nil + @spec get_native_coin_exchange_rate_from_db() :: Token.t() def get_native_coin_exchange_rate_from_db do today = case fetch_recent_history() do @@ -37,22 +29,48 @@ defmodule Explorer.Market do end if today do - Map.get(today, :closing_price) + %Token{ + usd_value: Map.get(today, :closing_price), + market_cap_usd: Map.get(today, :market_cap), + available_supply: nil, + total_supply: nil, + btc_value: nil, + id: nil, + last_updated: nil, + name: nil, + symbol: nil, + volume_24h_usd: nil + } else - nil + Token.null() end end + @doc """ + Get most recent exchange rate for the native coin from ETS or from DB. + """ + @spec get_coin_exchange_rate() :: Token.t() | nil + def get_coin_exchange_rate do + get_exchange_rate(Explorer.coin()) || get_native_coin_exchange_rate_from_db() || Token.null() + end + @doc false def bulk_insert_history(records) do records_without_zeroes = records |> Enum.reject(fn item -> - Decimal.equal?(item.closing_price, 0) && Decimal.equal?(item.opening_price, 0) + Map.has_key?(item, :opening_price) && Map.has_key?(item, :closing_price) && + Decimal.equal?(item.closing_price, 0) && + Decimal.equal?(item.opening_price, 0) end) # Enforce MarketHistory ShareLocks order (see docs: sharelocks.md) |> Enum.sort_by(& &1.date) Repo.insert_all(MarketHistory, records_without_zeroes, on_conflict: :nothing, conflict_target: [:date]) end + + @spec get_exchange_rate(String.t()) :: Token.t() | nil + defp get_exchange_rate(symbol) do + ExchangeRates.lookup(symbol) + end end diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index 10850177b7..aca3090625 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -9,6 +9,7 @@ defmodule Explorer.Market.MarketHistory do field(:closing_price, :decimal) field(:date, :date) field(:opening_price, :decimal) + field(:market_cap, :decimal) end @typedoc """ @@ -17,10 +18,12 @@ defmodule Explorer.Market.MarketHistory do * `:closing_price` - Closing price in USD. * `:date` - The date in UTC. * `:opening_price` - Opening price in USD. + * `:market_cap` - Market cap in USD. """ @type t :: %__MODULE__{ closing_price: Decimal.t(), date: Date.t(), - opening_price: Decimal.t() + opening_price: Decimal.t(), + market_cap: Decimal.t() } end diff --git a/apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs b/apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs new file mode 100644 index 0000000000..b0ddaca92c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20230530074105_market_history_add_market_cap.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.MarketHistoryAddMarketCap do + use Ecto.Migration + + def change do + alter table(:market_history) do + add(:market_cap, :decimal) + end + end +end diff --git a/apps/explorer/test/explorer/market/history/cataloger_test.exs b/apps/explorer/test/explorer/market/history/cataloger_test.exs index 433ad0fc1e..0acc04a7f2 100644 --- a/apps/explorer/test/explorer/market/history/cataloger_test.exs +++ b/apps/explorer/test/explorer/market/history/cataloger_test.exs @@ -5,7 +5,7 @@ defmodule Explorer.Market.History.CatalogerTest do alias Explorer.Market.MarketHistory alias Explorer.Market.History.Cataloger - alias Explorer.Market.History.Source.TestSource + alias Explorer.Market.History.Source.Price.TestSource alias Explorer.Repo setup do @@ -15,27 +15,58 @@ defmodule Explorer.Market.History.CatalogerTest do test "init" do assert {:ok, %{}} == Cataloger.init(:ok) - assert_received {:fetch_history, 365} + assert_received {:fetch_price_history, 365} end - test "handle_info with `{:fetch_history, days}`" do + test "handle_info with `{:fetch_price_history, days}`" do records = [%{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)}] - expect(TestSource, :fetch_history, fn 1 -> {:ok, records} end) + expect(TestSource, :fetch_price_history, fn 1 -> {:ok, records} end) set_mox_global() state = %{} - assert {:noreply, state} == Cataloger.handle_info({:fetch_history, 1}, state) - assert_receive {_ref, {1, 0, {:ok, ^records}}} + assert {:noreply, state} == Cataloger.handle_info({:fetch_price_history, 1}, state) + assert_receive {_ref, {:price_history, {1, 0, {:ok, ^records}}}} end - test "handle_info with successful task" do + test "handle_info with successful tasks (price and market cap)" do Application.put_env(:explorer, Cataloger, history_fetch_interval: 1) - record = %{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)} - state = %{} + record_price = %{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)} + record_market_cap = %{date: ~D[2018-04-01], market_cap: Decimal.new(100_500)} + + state = %{ + price_records: [ + record_price + ] + } + + assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, [record_price]}}}}, state) + assert_receive :fetch_market_cap_history + + assert {:noreply, state} == + Cataloger.handle_info({nil, {:market_cap_history, {0, {:ok, record_market_cap}}}}, state) + + assert Repo.get_by(MarketHistory, date: record_price.date) + end + + test "handle_info with successful price task" do + Application.put_env(:explorer, Cataloger, history_fetch_interval: 1) + record_price = %{date: ~D[2018-04-01], closing_price: Decimal.new(10), opening_price: Decimal.new(5)} + record_market_cap = nil + + state = %{ + price_records: [ + record_price + ] + } + + assert {:noreply, state} == Cataloger.handle_info({nil, {:price_history, {1, 0, {:ok, [record_price]}}}}, state) + assert_receive :fetch_market_cap_history + + assert {:noreply, state} == + Cataloger.handle_info({nil, {:market_cap_history, {0, {:ok, record_market_cap}}}}, state) - assert {:noreply, state} == Cataloger.handle_info({nil, {1, 0, {:ok, [record]}}}, state) - assert_receive {:fetch_history, 1} - assert Repo.get_by(MarketHistory, date: record.date) + assert record = Repo.get_by(MarketHistory, date: record_price.date) + assert record.market_cap == nil end test "handle info for DOWN message" do diff --git a/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs b/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs similarity index 90% rename from apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs rename to apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs index 1fc4223f72..2d8311b0ae 100644 --- a/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs +++ b/apps/explorer/test/explorer/market/history/source/price/crypto_compare_test.exs @@ -1,7 +1,7 @@ -defmodule Explorer.Market.History.Source.CryptoCompareTest do +defmodule Explorer.Market.History.Source.Price.CryptoCompareTest do use ExUnit.Case, async: false - alias Explorer.Market.History.Source.CryptoCompare + alias Explorer.Market.History.Source.Price.CryptoCompare alias Plug.Conn @json """ @@ -48,7 +48,7 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do } """ - describe "fetch_history/1" do + describe "fetch_price_history/1" do setup do bypass = Bypass.open() Application.put_env(:explorer, CryptoCompare, base_url: "http://localhost:#{bypass.port}") @@ -77,14 +77,14 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do } ] - assert {:ok, expected} == CryptoCompare.fetch_history(3) + assert {:ok, expected} == CryptoCompare.fetch_price_history(3) end test "with errored request", %{bypass: bypass} do error_text = ~S({"error": "server error"}) Bypass.expect(bypass, fn conn -> Conn.resp(conn, 500, error_text) end) - assert :error == CryptoCompare.fetch_history(3) + assert :error == CryptoCompare.fetch_price_history(3) end test "rejects empty prices", %{bypass: bypass} do @@ -138,7 +138,7 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do %{closing_price: Decimal.from_float(8804.32), date: ~D[2018-04-26], opening_price: Decimal.from_float(8873.57)} ] - assert {:ok, expected} == CryptoCompare.fetch_history(3) + assert {:ok, expected} == CryptoCompare.fetch_price_history(3) end end end diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 849fa0b6ae..5f07fdcc59 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -15,7 +15,7 @@ Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :auto) Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRates.Source) -Mox.defmock(Explorer.Market.History.Source.TestSource, for: Explorer.Market.History.Source) +Mox.defmock(Explorer.Market.History.Source.Price.TestSource, for: Explorer.Market.History.Source.Price) Mox.defmock(Explorer.History.TestHistorian, for: Explorer.History.Historian) Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)