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 48d84dd5d1..40a0fd3bbb 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
@@ -4,6 +4,7 @@ defmodule BlockScoutWeb.ChainController do
alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, Transaction}
+ alias Explorer.Chain.Supply.RSK
alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token
alias Explorer.Market
@@ -13,6 +14,15 @@ defmodule BlockScoutWeb.ChainController do
transaction_estimated_count = Chain.transaction_estimated_count()
block_count = Chain.block_estimated_count()
+ market_cap_calculation =
+ case Application.get_env(:explorer, :supply) do
+ RSK ->
+ RSK
+
+ _ ->
+ :standard
+ end
+
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
render(
@@ -22,6 +32,7 @@ defmodule BlockScoutWeb.ChainController do
average_block_time: AverageBlockTime.average_block_time(),
exchange_rate: exchange_rate,
chart_data_path: market_history_chart_path(conn, :show),
+ market_cap_calculation: market_cap_calculation,
transaction_estimated_count: transaction_estimated_count,
transactions_path: recent_transactions_path(conn, :index),
block_count: block_count
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
index b5d7283a03..17bd9c4afe 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
@@ -30,7 +30,7 @@
<%= gettext "Market Cap" %>
-
+
diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
index 7f49d2d239..506d6ff3c8 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
@@ -2,4 +2,12 @@ defmodule BlockScoutWeb.ChainView do
use BlockScoutWeb, :view
alias BlockScoutWeb.LayoutView
+
+ defp market_cap(:standard, exchange_rate) do
+ exchange_rate.market_cap_usd
+ end
+
+ defp market_cap(module, exchange_rate) do
+ module.market_cap(exchange_rate)
+ end
end
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index 9a7d9f4296..9a601e6c68 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -71,8 +71,15 @@ else
config :explorer, Explorer.Staking.EpochCounter, enabled: false
end
-if System.get_env("SUPPLY_MODULE") == "TokenBridge" do
- config :explorer, supply: Explorer.Chain.Supply.TokenBridge
+case System.get_env("SUPPLY_MODULE") do
+ "TokenBridge" ->
+ config :explorer, supply: Explorer.Chain.Supply.TokenBridge
+
+ "rsk" ->
+ config :explorer, supply: Explorer.Chain.Supply.RSK
+
+ _ ->
+ :ok
end
if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do
diff --git a/apps/explorer/lib/explorer/chain/supply/rsk.ex b/apps/explorer/lib/explorer/chain/supply/rsk.ex
new file mode 100644
index 0000000000..09a02252a2
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/supply/rsk.ex
@@ -0,0 +1,103 @@
+defmodule Explorer.Chain.Supply.RSK do
+ @moduledoc """
+ Defines the supply API for calculating supply for coins from RSK.
+ """
+
+ use Explorer.Chain.Supply
+
+ import Ecto.Query, only: [from: 2]
+
+ alias Explorer.Chain.Address.CoinBalance
+ alias Explorer.Chain.{Block, Wei}
+ alias Explorer.ExchangeRates.Token
+ alias Explorer.{Market, Repo}
+
+ def market_cap(exchange_rate) do
+ circulating() * exchange_rate.usd_value
+ end
+
+ @doc "Equivalent to getting the circulating value "
+ def supply_for_days(days) do
+ now = Timex.now()
+
+ balances_query =
+ from(balance in CoinBalance,
+ join: block in Block,
+ on: block.number == balance.block_number,
+ where: block.consensus == true,
+ where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
+ where: block.timestamp > ^Timex.shift(now, days: -days),
+ distinct: fragment("date_trunc('day', ?)", block.timestamp),
+ select: {block.timestamp, balance.value}
+ )
+
+ balance_before_query =
+ from(balance in CoinBalance,
+ join: block in Block,
+ on: block.number == balance.block_number,
+ where: block.consensus == true,
+ where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
+ where: block.timestamp <= ^Timex.shift(Timex.now(), days: -days),
+ order_by: [desc: block.timestamp],
+ limit: 1,
+ select: balance.value
+ )
+
+ by_day =
+ balances_query
+ |> Repo.all()
+ |> Enum.into(%{}, fn {timestamp, value} ->
+ {Timex.to_date(timestamp), value}
+ end)
+
+ starting = Repo.one(balance_before_query) || wei!(0)
+
+ result =
+ -days..0
+ |> Enum.reduce({%{}, starting.value}, fn i, {days, last} ->
+ date =
+ now
+ |> Timex.shift(days: i)
+ |> Timex.to_date()
+
+ case Map.get(by_day, date) do
+ nil ->
+ {Map.put(days, date, last), last}
+
+ value ->
+ {Map.put(days, date, value.value), value.value}
+ end
+ end)
+ |> elem(0)
+
+ {:ok, result}
+ end
+
+ def circulating do
+ query =
+ from(balance in CoinBalance,
+ join: block in Block,
+ on: block.number == balance.block_number,
+ where: block.consensus == true,
+ where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
+ order_by: [desc: block.timestamp],
+ limit: 1,
+ select: balance.value
+ )
+
+ Repo.one(query) || wei!(0)
+ end
+
+ defp wei!(value) do
+ {:ok, wei} = Wei.cast(value)
+ wei
+ end
+
+ def total do
+ 21_000_000
+ end
+
+ def exchange_rate do
+ Market.get_exchange_rate(Explorer.coin()) || Token.null()
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/supply/rsk_test.exs b/apps/explorer/test/explorer/chain/supply/rsk_test.exs
new file mode 100644
index 0000000000..40aa0a831b
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/supply/rsk_test.exs
@@ -0,0 +1,139 @@
+defmodule Explorer.Chain.Supply.RSKTest do
+ use Explorer.DataCase
+
+ alias Explorer.Chain.Supply.RSK
+ alias Explorer.Chain.Wei
+
+ @coin_address "0x0000000000000000000000000000000001000006"
+
+ defp wei!(value) do
+ {:ok, wei} = Wei.cast(value)
+ wei
+ end
+
+ test "total is 21_000_000" do
+ assert RSK.total() == 21_000_000
+ end
+
+ describe "circulating/0" do
+ test "with no balance" do
+ assert RSK.circulating() == wei!(0)
+ end
+
+ test "with a balance" do
+ address = insert(:address, hash: @coin_address)
+ insert(:block, number: 0)
+
+ insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
+
+ assert RSK.circulating() == wei!(10)
+ end
+ end
+
+ defp date(now, shift \\ []) do
+ now
+ |> Timex.shift(shift)
+ |> Timex.to_date()
+ end
+
+ defp dec(number) do
+ Decimal.new(number)
+ end
+
+ describe "supply_for_days/1" do
+ test "when there is no balance" do
+ now = Timex.now()
+
+ assert RSK.supply_for_days(2) ==
+ {:ok,
+ %{
+ date(now, days: -2) => dec(0),
+ date(now, days: -1) => dec(0),
+ date(now) => dec(0)
+ }}
+ end
+
+ test "when there is a single balance before the days, that balance is used" do
+ address = insert(:address, hash: @coin_address)
+ now = Timex.now()
+
+ insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
+
+ insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
+
+ assert RSK.supply_for_days(2) ==
+ {:ok,
+ %{
+ date(now, days: -2) => dec(10),
+ date(now, days: -1) => dec(10),
+ date(now) => dec(10)
+ }}
+ end
+
+ test "when there is a balance for one of the days, days after it use that balance" do
+ address = insert(:address, hash: @coin_address)
+ now = Timex.now()
+
+ insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
+ insert(:block, number: 1, timestamp: Timex.shift(now, days: -1))
+
+ insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
+
+ insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 1)
+
+ assert RSK.supply_for_days(2) ==
+ {:ok,
+ %{
+ date(now, days: -2) => dec(10),
+ date(now, days: -1) => dec(20),
+ date(now) => dec(20)
+ }}
+ end
+
+ test "when there is a balance for the first day, that balance is used" do
+ address = insert(:address, hash: @coin_address)
+ now = Timex.now()
+
+ insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
+ insert(:block, number: 1, timestamp: Timex.shift(now, days: -2))
+ insert(:block, number: 2, timestamp: Timex.shift(now, days: -1))
+
+ insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0)
+
+ insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1)
+
+ insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2)
+
+ assert RSK.supply_for_days(2) ==
+ {:ok,
+ %{
+ date(now, days: -2) => dec(10),
+ date(now, days: -1) => dec(20),
+ date(now) => dec(20)
+ }}
+ end
+
+ test "when there is a balance for all days, they are each used correctly" do
+ address = insert(:address, hash: @coin_address)
+ now = Timex.now()
+
+ insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
+ insert(:block, number: 1, timestamp: Timex.shift(now, days: -2))
+ insert(:block, number: 2, timestamp: Timex.shift(now, days: -1))
+ insert(:block, number: 3, timestamp: now)
+
+ insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0)
+ insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1)
+ insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2)
+ insert(:fetched_balance, value: 30, address_hash: address.hash, block_number: 3)
+
+ assert RSK.supply_for_days(2) ==
+ {:ok,
+ %{
+ date(now, days: -2) => dec(10),
+ date(now, days: -1) => dec(20),
+ date(now) => dec(30)
+ }}
+ end
+ end
+end