Skip to content

Commit

Permalink
Exchange rates CoinMarketCap source module/CoinGecko API key support
Browse files Browse the repository at this point in the history
  • Loading branch information
vbaranov committed May 27, 2022
1 parent 9e64d36 commit a94f1be
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .dialyzer-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ lib/block_scout_web/views/layout_view.ex:145: The call 'Elixir.Poison.Parser':'p
lib/block_scout_web/views/layout_view.ex:237: The call 'Elixir.Poison.Parser':'parse!'
lib/explorer/smart_contract/reader.ex:435
lib/indexer/fetcher/token_total_supply_on_demand.ex:16
lib/explorer/exchange_rates/source.ex:110
lib/explorer/exchange_rates/source.ex:113
lib/explorer/exchange_rates/source.ex:116
lib/explorer/smart_contract/solidity/verifier.ex:223
lib/block_scout_web/templates/address_contract/index.html.eex:158
lib/block_scout_web/templates/address_contract/index.html.eex:195
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Current

### Features
- [#5613](https://github.com/blockscout/blockscout/pull/5613) - Exchange rates CoinMarketCap source module
- [#5588](https://github.com/blockscout/blockscout/pull/5588) - Add broadcasting of coin balance
- [#5479](https://github.com/blockscout/blockscout/pull/5479) - Remake of solidity verifier module; Verification UX improvements
- [#5540](https://github.com/blockscout/blockscout/pull/5540) - Tx page: scroll to selected tab's data
Expand Down
22 changes: 16 additions & 6 deletions apps/explorer/config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ disable_webapp = System.get_env("DISABLE_WEBAPP")
config :explorer,
ecto_repos: [Explorer.Repo],
coin: System.get_env("COIN") || "POA",
coingecko_coin_id: System.get_env("COINGECKO_COIN_ID"),
token_functions_reader_max_retries: 3,
allowed_evm_versions:
System.get_env("ALLOWED_EVM_VERSIONS") ||
Expand Down Expand Up @@ -142,7 +141,22 @@ config :explorer, Explorer.Counters.Bridge,
update_interval_in_seconds: bridge_market_cap_update_interval || 30 * 60,
disable_lp_tokens_in_market_cap: System.get_env("DISABLE_LP_TOKENS_IN_MARKET_CAP") == "true"

config :explorer, Explorer.ExchangeRates, enabled: System.get_env("DISABLE_EXCHANGE_RATES") != "true", store: :ets
config :explorer, Explorer.ExchangeRates,
enabled: System.get_env("DISABLE_EXCHANGE_RATES") != "true",
store: :ets,
coingecko_coin_id: System.get_env("EXCHANGE_RATES_COINGECKO_COIN_ID"),
coingecko_api_key: System.get_env("EXCHANGE_RATES_COINGECKO_API_KEY"),
coinmarketcap_api_key: System.get_env("EXCHANGE_RATES_COINMARKETCAP_API_KEY")

exchange_rates_source =
cond do
System.get_env("EXCHANGE_RATES_SOURCE") == "token_bridge" -> Explorer.ExchangeRates.Source.TokenBridge
System.get_env("EXCHANGE_RATES_SOURCE") == "coin_gecko" -> Explorer.ExchangeRates.Source.CoinGecko
System.get_env("EXCHANGE_RATES_SOURCE") == "coin_market_cap" -> Explorer.ExchangeRates.Source.CoinMarketCap
true -> Explorer.ExchangeRates.Source.CoinGecko
end

config :explorer, Explorer.ExchangeRates.Source, source: exchange_rates_source

config :explorer, Explorer.KnownTokens, enabled: System.get_env("DISABLE_KNOWN_TOKENS") != "true", store: :ets

Expand Down Expand Up @@ -221,10 +235,6 @@ case System.get_env("SUPPLY_MODULE") do
:ok
end

if System.get_env("SOURCE_MODULE") == "TokenBridge" do
config :explorer, Explorer.ExchangeRates.Source, source: Explorer.ExchangeRates.Source.TokenBridge
end

config :explorer,
solc_bin_api_url: "https://solc-bin.ethereum.org",
checksum_function: System.get_env("CHECKSUM_FUNCTION") && String.to_atom(System.get_env("CHECKSUM_FUNCTION"))
Expand Down
4 changes: 3 additions & 1 deletion apps/explorer/lib/explorer/chain/supply/token_bridge.ex
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ defmodule Explorer.Chain.Supply.TokenBridge do
def total_market_cap_from_omni_bridge, do: Bridge.fetch_omni_bridge_market_cap()

def total_chain_supply do
source = Application.get_env(:explorer, Source)[:source]

usd_value =
case Source.fetch_exchange_rates(Source.CoinGecko) do
case Source.fetch_exchange_rates(source) do
{:ok, [rates]} ->
rates.usd_value

Expand Down
28 changes: 17 additions & 11 deletions apps/explorer/lib/explorer/exchange_rates/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,28 @@ defmodule Explorer.ExchangeRates.Source do
@spec fetch_exchange_rates(module) :: {:ok, [Token.t()]} | {:error, any}
def fetch_exchange_rates(source \\ exchange_rates_source()) do
source_url = source.source_url()
fetch_exchange_rates_request(source, source_url)
fetch_exchange_rates_request(source, source_url, source.headers())
end

@spec fetch_exchange_rates_for_token(String.t()) :: {:ok, [Token.t()]} | {:error, any}
def fetch_exchange_rates_for_token(symbol) do
source_url = Source.CoinGecko.source_url(symbol)
fetch_exchange_rates_request(Source.CoinGecko, source_url)
source = Application.get_env(:explorer, Source)[:source]
source_url = source.source_url(symbol)
fetch_exchange_rates_request(source, source_url, source.headers())
end

@spec fetch_exchange_rates_for_token_address(String.t()) :: {:ok, [Token.t()]} | {:error, any}
def fetch_exchange_rates_for_token_address(address_hash) do
source_url = Source.CoinGecko.source_url(address_hash)
fetch_exchange_rates_request(Source.CoinGecko, source_url)
source = Application.get_env(:explorer, Source)[:source]
source_url = source.source_url(address_hash)
fetch_exchange_rates_request(source, source_url, source.headers())
end

defp fetch_exchange_rates_request(_source, source_url) when is_nil(source_url), do: {:error, "Source URL is nil"}
defp fetch_exchange_rates_request(_source, source_url, _headers) when is_nil(source_url),
do: {:error, "Source URL is nil"}

defp fetch_exchange_rates_request(source, source_url) do
case http_request(source_url) do
defp fetch_exchange_rates_request(source, source_url, headers) do
case http_request(source_url, headers) do
{:ok, result} = resp ->
if is_map(result) do
result_formatted =
Expand Down Expand Up @@ -58,6 +61,8 @@ defmodule Explorer.ExchangeRates.Source do

@callback source_url(String.t()) :: String.t() | :ignore

@callback headers() :: [any]

def headers do
[{"Content-Type", "application/json"}]
end
Expand All @@ -82,16 +87,17 @@ defmodule Explorer.ExchangeRates.Source do

@spec exchange_rates_source() :: module()
defp exchange_rates_source do
config(:source) || Explorer.ExchangeRates.Source.CoinGecko
source = Application.get_env(:explorer, Source)[:source]
config(:source) || source
end

@spec config(atom()) :: term
defp config(key) do
Application.get_env(:explorer, __MODULE__, [])[key]
end

def http_request(source_url) do
case HTTPoison.get(source_url, headers()) do
def http_request(source_url, additional_headers) do
case HTTPoison.get(source_url, headers() ++ additional_headers) do
{:ok, %Response{body: body, status_code: 200}} ->
parse_http_success_response(body)

Expand Down
93 changes: 51 additions & 42 deletions apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
Adapter for fetching exchange rates from https://coingecko.com
"""

alias Explorer.Chain
alias Explorer.{Chain, ExchangeRates}
alias Explorer.ExchangeRates.{Source, Token}

import Source, only: [to_decimal: 1]
Expand Down Expand Up @@ -44,45 +44,9 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
@impl Source
def format_data(_), do: []

defp get_last_updated(market_data) do
last_updated_data = market_data && market_data["last_updated"]

if last_updated_data do
{:ok, last_updated, 0} = DateTime.from_iso8601(last_updated_data)
last_updated
else
nil
end
end

defp get_current_price(market_data) do
if market_data["current_price"] do
to_decimal(market_data["current_price"]["usd"])
else
1
end
end

defp get_btc_value(id, market_data) do
case get_btc_price() do
{:ok, price} ->
btc_price = to_decimal(price)
current_price = get_current_price(market_data)

if id != "btc" && current_price && btc_price do
Decimal.div(current_price, btc_price)
else
1
end

_ ->
1
end
end

@impl Source
def source_url do
explicit_coin_id = Application.get_env(:explorer, :coingecko_coin_id)
explicit_coin_id = Application.get_env(:explorer, ExchangeRates)[:coingecko_coin_id]

{:ok, id} =
if explicit_coin_id do
Expand Down Expand Up @@ -123,8 +87,13 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
end
end

defp base_url do
config(:base_url) || "https://api.coingecko.com/api/v3"
@impl Source
def headers do
[{"X-Cg-Pro-Api-Key", "#{api_key()}"}]
end

defp api_key do
Application.get_env(:explorer, ExchangeRates)[:coingecko_api_key]
end

def coin_id do
Expand All @@ -143,7 +112,7 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do

symbol_downcase = String.downcase(symbol)

case Source.http_request(url) do
case Source.http_request(url, headers()) do
{:ok, data} = resp ->
if is_list(data) do
symbol_data =
Expand All @@ -166,10 +135,50 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
end
end

defp get_last_updated(market_data) do
last_updated_data = market_data && market_data["last_updated"]

if last_updated_data do
{:ok, last_updated, 0} = DateTime.from_iso8601(last_updated_data)
last_updated
else
nil
end
end

defp get_current_price(market_data) do
if market_data["current_price"] do
to_decimal(market_data["current_price"]["usd"])
else
1
end
end

defp get_btc_value(id, market_data) do
case get_btc_price() do
{:ok, price} ->
btc_price = to_decimal(price)
current_price = get_current_price(market_data)

if id != "btc" && current_price && btc_price do
Decimal.div(current_price, btc_price)
else
1
end

_ ->
1
end
end

defp base_url do
config(:base_url) || "https://pro-api.coingecko.com/api/v3"
end

defp get_btc_price(currency \\ "usd") do
url = "#{base_url()}/exchange_rates"

case Source.http_request(url) do
case Source.http_request(url, headers()) do
{:ok, data} = resp ->
if is_map(data) do
current_price = data["rates"][currency]["value"]
Expand Down
Loading

0 comments on commit a94f1be

Please sign in to comment.