parent
4f461d818f
commit
678927883f
@ -1,116 +0,0 @@ |
||||
defmodule BlockScoutWeb.BridgedTokensController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] |
||||
|
||||
alias BlockScoutWeb.{BridgedTokensView, Controller} |
||||
alias Explorer.Chain |
||||
alias Phoenix.View |
||||
|
||||
def show(conn, %{"type" => "JSON", "id" => "eth"} = params) do |
||||
get_items(conn, params, :eth) |
||||
end |
||||
|
||||
def show(conn, %{"type" => "JSON", "id" => "bsc"} = params) do |
||||
get_items(conn, params, :bsc) |
||||
end |
||||
|
||||
def show(conn, %{"id" => "eth"}) do |
||||
render(conn, "index.html", |
||||
current_path: Controller.current_full_path(conn), |
||||
chain: "Ethereum", |
||||
chain_id: 1, |
||||
destination: :eth |
||||
) |
||||
end |
||||
|
||||
def show(conn, %{"id" => "bsc"}) do |
||||
render(conn, "index.html", |
||||
current_path: Controller.current_full_path(conn), |
||||
chain: "Binance Smart Chain", |
||||
chain_id: 56, |
||||
destination: :bsc |
||||
) |
||||
end |
||||
|
||||
def show(conn, _params) do |
||||
not_found(conn) |
||||
end |
||||
|
||||
def index(conn, %{"type" => "JSON"} = params) do |
||||
get_items(conn, params, :eth) |
||||
end |
||||
|
||||
def index(conn, _params) do |
||||
render(conn, "index.html", |
||||
current_path: Controller.current_full_path(conn), |
||||
chain: "Ethereum", |
||||
chain_id: 1, |
||||
destination: :eth |
||||
) |
||||
end |
||||
|
||||
defp get_items(conn, params, destination) do |
||||
filter = |
||||
if Map.has_key?(params, "filter") do |
||||
Map.get(params, "filter") |
||||
else |
||||
nil |
||||
end |
||||
|
||||
paging_params = |
||||
params |
||||
|> paging_options() |
||||
|
||||
from_api = false |
||||
tokens = Chain.list_top_bridged_tokens(destination, filter, from_api, paging_params) |
||||
|
||||
{tokens_page, next_page} = split_list_by_page(tokens) |
||||
|
||||
next_page_path = |
||||
case next_page_params(next_page, tokens_page, params) do |
||||
nil -> |
||||
nil |
||||
|
||||
next_page_params -> |
||||
bridged_tokens_path( |
||||
conn, |
||||
:show, |
||||
destination, |
||||
Map.delete(next_page_params, "type") |
||||
) |
||||
end |
||||
|
||||
items_count_str = Map.get(params, "items_count") |
||||
|
||||
items_count = |
||||
if items_count_str do |
||||
{items_count, _} = Integer.parse(items_count_str) |
||||
items_count |
||||
else |
||||
0 |
||||
end |
||||
|
||||
items = |
||||
tokens_page |
||||
|> Enum.with_index(1) |
||||
|> Enum.map(fn {[token, bridged_token], index} -> |
||||
View.render_to_string( |
||||
BridgedTokensView, |
||||
"_tile.html", |
||||
token: token, |
||||
bridged_token: bridged_token, |
||||
destination: destination, |
||||
index: items_count + index |
||||
) |
||||
end) |
||||
|
||||
json( |
||||
conn, |
||||
%{ |
||||
items: items, |
||||
next_page_path: next_page_path |
||||
} |
||||
) |
||||
end |
||||
end |
@ -1,97 +0,0 @@ |
||||
<tr data-identifier-hash="<%= @token.contract_address_hash %>"> |
||||
<td class="stakes-td"> |
||||
<!-- incremented number by order in the list --> |
||||
<span class="color-lighten"> |
||||
<%= @index %> |
||||
</span> |
||||
</td> |
||||
<td class="token-icon"> |
||||
<%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %> |
||||
<% foreign_chain_id = if Map.has_key?(@bridged_token, :foreign_chain_id), do: @bridged_token.foreign_chain_id, else: nil %> |
||||
<% chain_id_for_token_icon = if foreign_chain_id, do: foreign_chain_id |> Decimal.to_integer() |> to_string(), else: System.get_env("CHAIN_ID") %> |
||||
<% foreign_token_contract_address_hash = if Map.has_key?(@bridged_token, :foreign_token_contract_address_hash), do: Address.checksum(@bridged_token.foreign_token_contract_address_hash), else: nil %> |
||||
<% token_hash_for_token_icon = if foreign_token_contract_address_hash, do: foreign_token_contract_address_hash, else: Address.checksum(@token.contract_address_hash) %> |
||||
<%= |
||||
render BlockScoutWeb.TokensView, |
||||
"_token_icon.html", |
||||
chain_id: chain_id_for_token_icon, |
||||
address: token_hash_for_token_icon |
||||
%> |
||||
<% end %> |
||||
</td> |
||||
<td class="stakes-td token-name"> |
||||
<% token = token_display_name(@token) %> |
||||
<div class="centered-container"> |
||||
<div> |
||||
<div style="display: table;"> |
||||
<%= link(token, |
||||
to: token_path(BlockScoutWeb.Endpoint, :show, @token.contract_address_hash), |
||||
"data-test": "token_link", |
||||
class: "text-truncate pre-wrap") %> |
||||
<%= if owl_token_amb?(@token.contract_address.hash) do %> |
||||
<span |
||||
data-toggle="tooltip" |
||||
data-placement="top" |
||||
data-html="true" |
||||
title="<%= owl_token_amb_info() %>"> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_info.html", extra_class: "ml-1" %> |
||||
</span> |
||||
<% end %> |
||||
<%= if owl_token_omni?(@token.contract_address.hash) do %> |
||||
<span |
||||
data-toggle="tooltip" |
||||
data-placement="top" |
||||
data-html="true" |
||||
title="<%= owl_token_omni_info() %>"> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_info.html", extra_class: "ml-1" %> |
||||
</span> |
||||
<% end %> |
||||
<%= if @bridged_token.custom_metadata do %> |
||||
<div class="card-subtitle list-title-description" style="display: table-row; text-align: left"><%= @bridged_token.custom_metadata %></div> |
||||
<% end %> |
||||
</div> |
||||
</div> |
||||
<%= if @bridged_token && @bridged_token.type do %> |
||||
<div class="bs-label right <%= @bridged_token.type %>"><%= String.upcase(@bridged_token.type) %></div> |
||||
<% end %> |
||||
<%= if @bridged_token && @bridged_token.foreign_chain_id do %> |
||||
<% tag = Chain.chain_id_display_name(@bridged_token.foreign_chain_id) %> |
||||
<div class="bs-label right ml-2 destination-<%= tag %>"><%= String.upcase(tag) %></div> |
||||
<% end %> |
||||
</div> |
||||
</td> |
||||
<td class="stakes-td"> |
||||
<%= render BlockScoutWeb.AddressView, |
||||
"_link.html", |
||||
address: @token.contract_address, |
||||
contract: true, |
||||
use_custom_tooltip: false |
||||
%> |
||||
</td> |
||||
<td class="stakes-td token-total-supply"> |
||||
<%= if decimals?(@token) do %> |
||||
<span data-test="token_supply"><%= format_according_to_decimals(@token.total_supply, @token.decimals) %></span> |
||||
<% else %> |
||||
<span data-test="token_supply"><%= format_integer_to_currency(@token.total_supply) %></span> |
||||
<% end %> <%= @token.symbol %> |
||||
</td> |
||||
<td class="stakes-td"> |
||||
<span class="mr-4"> |
||||
<span data-test="transaction_count"> |
||||
<%= @token.holder_count %> |
||||
</span> |
||||
</span> |
||||
</td> |
||||
<%= if @destination == :eth do %> |
||||
<td class="stakes-td"> |
||||
<span class="mr-4"> |
||||
<span data-test="tvl"> |
||||
<% usd_val = bridged_token_usd_cap(@bridged_token, @token) %> |
||||
<%= if Decimal.compare(usd_val, 0) == :gt do %> |
||||
<%= ChainView.format_usd_value(usd_val) %> |
||||
<% end %> |
||||
</span> |
||||
</span> |
||||
</td> |
||||
<% end %> |
||||
</tr> |
@ -1,70 +0,0 @@ |
||||
<section class="container" data-page="tokens"> |
||||
<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> |
||||
<div class="card"> |
||||
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>"> |
||||
<div class="tokens-list-search-input-outer-container"> |
||||
<% tag = Chain.chain_id_display_name(@chain_id) %> |
||||
<h1 class="card-title"><div class="title-with-label"><%= gettext "Bridged Tokens from " %><%= @chain %></div><div class="bs-label ml-2 destination-<%= tag %>"><%= String.upcase(tag) %></div></h1> |
||||
<label class="tokens-list-search-input-container"> |
||||
<input data-search-field="" class="form-control tokens-list-search-input search-input" type="text" name="filter" placeholder="Token name or symbol" id="search-text-input" /> |
||||
</label> |
||||
</div> |
||||
<div> |
||||
<p class="card-subtitle list-title-description">List of the tokens bridged through OmniBridge <span class="bs-label omni">OMNI</span> and Arbitrary Message Bridge <span class="bs-label amb">AMB</span> extensions</p> |
||||
<div class="list-top-pagination-container-wrapper"> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="addresses-table-container"> |
||||
<div class="stakes-table-container"> |
||||
<table> |
||||
<thead> |
||||
<tr> |
||||
<th class="stakes-table-th"> |
||||
<div class="stakes-table-th-content"> </div> |
||||
</th> |
||||
<th class="stakes-table-th"> |
||||
<div> </div> |
||||
</th> |
||||
<th class="stakes-table-th"> |
||||
<div class="stakes-table-th-content">Token</div> |
||||
</th> |
||||
<th class="stakes-table-th"> |
||||
<div class="stakes-table-th-content">Address</div> |
||||
</th> |
||||
<th class="stakes-table-th"> |
||||
<div class="stakes-table-th-content"> |
||||
Total Supply |
||||
</div> |
||||
</th> |
||||
<th class="stakes-table-th"> |
||||
<div class="stakes-table-th-content"> |
||||
Holders Count |
||||
</div> |
||||
</th> |
||||
<%= if @destination == :eth do %> |
||||
<th class="stakes-table-th"> |
||||
<div class="stakes-table-th-content"> |
||||
USD Value |
||||
</div> |
||||
</th> |
||||
<% end %> |
||||
</tr> |
||||
</thead> |
||||
<tbody data-items data-selector="top-bridged-tokens-list"> |
||||
<%= if @destination == :eth do %> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 7 %> |
||||
<% else %> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 6 %> |
||||
<% end %> |
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
|
||||
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> |
||||
</div> |
||||
</div> |
||||
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/tokens.js") %>"></script> |
||||
</section> |
@ -1,49 +0,0 @@ |
||||
defmodule BlockScoutWeb.BridgedTokensView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.ChainView |
||||
alias Explorer.Chain |
||||
alias Explorer.Chain.{Address, BridgedToken, Token} |
||||
|
||||
@owl_token_amb "0x0905Ab807F8FD040255F0cF8fa14756c1D824931" |
||||
@owl_token_omni "0x750eCf8c11867Ce5Dbc556592c5bb1E0C6d16538" |
||||
|
||||
def decimals?(%Token{decimals: nil}), do: false |
||||
def decimals?(%Token{decimals: _}), do: true |
||||
|
||||
def token_display_name(%Token{name: nil}), do: "" |
||||
|
||||
def token_display_name(%Token{name: name}), do: name |
||||
|
||||
def owl_token_amb?(address_hash) do |
||||
to_string(address_hash) == String.downcase(@owl_token_amb) |
||||
end |
||||
|
||||
def owl_token_omni?(address_hash) do |
||||
to_string(address_hash) == String.downcase(@owl_token_omni) |
||||
end |
||||
|
||||
def owl_token_amb_info do |
||||
"<div class='custom-tooltip-header'>OWL token bridged through AMB extension with support of <i>burnOWL</i> method. It is recommended to use.</div>" |
||||
end |
||||
|
||||
def owl_token_omni_info do |
||||
"<div class='custom-tooltip-header'>OWL token bridged through OmniBridge without support of <i>burnOWL</i> method. It is not recommended to use.</div>" |
||||
end |
||||
|
||||
@doc """ |
||||
Calculates capitalization of the bridged token in USD. |
||||
""" |
||||
@spec bridged_token_usd_cap(BridgedToken.t(), Token.t()) :: any() |
||||
def bridged_token_usd_cap(bridged_token, token) do |
||||
if bridged_token.custom_cap do |
||||
bridged_token.custom_cap |
||||
else |
||||
if bridged_token.exchange_rate && token.total_supply do |
||||
Decimal.mult(bridged_token.exchange_rate, divide_decimals(token.total_supply, token.decimals)) |
||||
else |
||||
Decimal.new(0) |
||||
end |
||||
end |
||||
end |
||||
end |
File diff suppressed because it is too large
Load Diff
@ -1,97 +0,0 @@ |
||||
defmodule Explorer.Chain.BridgedToken do |
||||
@moduledoc """ |
||||
Represents a bridged token. |
||||
|
||||
""" |
||||
|
||||
use Explorer.Schema |
||||
|
||||
import Ecto.Changeset |
||||
|
||||
alias Explorer.Chain.{Address, BridgedToken, Hash, Token} |
||||
alias Explorer.Repo |
||||
|
||||
@typedoc """ |
||||
* `foreign_chain_id` - chain ID of a foreign token |
||||
* `foreign_token_contract_address_hash` - Foreign token's contract hash |
||||
* `home_token_contract_address` - The `t:Address.t/0` of the home token's contract |
||||
* `home_token_contract_address_hash` - Home token's contract hash foreign key |
||||
* `custom_metadata` - Arbitrary string with custom metadata. For instance, tokens/weights for Balance tokens |
||||
* `custom_cap` - Custom capitalization for this token |
||||
* `lp_token` - Boolean flag: LP token or not |
||||
* `type` - omni/amb |
||||
""" |
||||
@type t :: %BridgedToken{ |
||||
foreign_chain_id: Decimal.t(), |
||||
foreign_token_contract_address_hash: Hash.Address.t(), |
||||
home_token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(), |
||||
home_token_contract_address_hash: Hash.Address.t(), |
||||
custom_metadata: String.t(), |
||||
custom_cap: Decimal.t(), |
||||
lp_token: boolean(), |
||||
type: String.t(), |
||||
exchange_rate: Decimal.t() |
||||
} |
||||
|
||||
@derive {Poison.Encoder, |
||||
except: [ |
||||
:__meta__, |
||||
:home_token_contract_address, |
||||
:inserted_at, |
||||
:updated_at |
||||
]} |
||||
|
||||
@derive {Jason.Encoder, |
||||
except: [ |
||||
:__meta__, |
||||
:home_token_contract_address, |
||||
:inserted_at, |
||||
:updated_at |
||||
]} |
||||
|
||||
@primary_key false |
||||
schema "bridged_tokens" do |
||||
field(:foreign_chain_id, :decimal) |
||||
field(:foreign_token_contract_address_hash, Hash.Address) |
||||
field(:custom_metadata, :string) |
||||
field(:custom_cap, :decimal) |
||||
field(:lp_token, :boolean) |
||||
field(:type, :string) |
||||
field(:exchange_rate, :decimal) |
||||
|
||||
belongs_to( |
||||
:home_token_contract_address, |
||||
Token, |
||||
foreign_key: :home_token_contract_address_hash, |
||||
primary_key: true, |
||||
references: :contract_address_hash, |
||||
type: Hash.Address |
||||
) |
||||
|
||||
timestamps() |
||||
end |
||||
|
||||
@required_attrs ~w(home_token_contract_address_hash)a |
||||
@optional_attrs ~w(foreign_chain_id foreign_token_contract_address_hash custom_metadata custom_cap boolean type exchange_rate)a |
||||
|
||||
@doc false |
||||
def changeset(%BridgedToken{} = bridged_token, params \\ %{}) do |
||||
bridged_token |
||||
|> cast(params, @required_attrs ++ @optional_attrs) |
||||
|> validate_required(@required_attrs) |
||||
|> foreign_key_constraint(:home_token_contract_address) |
||||
|> unique_constraint(:home_token_contract_address_hash) |
||||
end |
||||
|
||||
def get_unprocessed_mainnet_lp_tokens_list do |
||||
query = |
||||
from(bt in BridgedToken, |
||||
where: bt.foreign_chain_id == ^1, |
||||
where: is_nil(bt.lp_token) or bt.lp_token == true, |
||||
select: bt |
||||
) |
||||
|
||||
query |
||||
|> Repo.all() |
||||
end |
||||
end |
@ -1,298 +0,0 @@ |
||||
defmodule Explorer.Chain.Supply.TokenBridge do |
||||
@moduledoc """ |
||||
Defines the supply API for calculating the supply based on Token Bridge. |
||||
""" |
||||
|
||||
use Explorer.Chain.Supply |
||||
|
||||
import Ecto.Query, |
||||
only: [ |
||||
from: 2 |
||||
] |
||||
|
||||
alias Explorer.Chain.{BridgedToken, Token, Wei} |
||||
alias Explorer.Chain.Cache.TokenExchangeRate, as: TokenExchangeRateCache |
||||
alias Explorer.Counters.Bridge |
||||
alias Explorer.ExchangeRates.Source |
||||
alias Explorer.{CustomContractsHelpers, Repo} |
||||
alias Explorer.SmartContract.Reader |
||||
|
||||
@token_bridge_contract_address "0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6" |
||||
@total_burned_coins_abi %{ |
||||
"type" => "function", |
||||
"stateMutability" => "view", |
||||
"payable" => false, |
||||
"outputs" => [%{"type" => "uint256", "name" => ""}], |
||||
"name" => "totalBurntCoins", |
||||
"inputs" => [], |
||||
"constant" => true |
||||
} |
||||
# 0e8162ba=keccak256(totalBurntCoins()) |
||||
@total_burned_coins_params %{"0e8162ba" => []} |
||||
|
||||
@block_reward_contract_abi %{ |
||||
"type" => "function", |
||||
"stateMutability" => "view", |
||||
"payable" => false, |
||||
"outputs" => [%{"type" => "address", "name" => ""}], |
||||
"name" => "blockRewardContract", |
||||
"inputs" => [], |
||||
"constant" => true |
||||
} |
||||
|
||||
# 56b54bae=keccak256(blockRewardContract()) |
||||
@block_reward_contract_params %{"56b54bae" => []} |
||||
|
||||
@total_minted_coins_abi %{ |
||||
"type" => "function", |
||||
"stateMutability" => "view", |
||||
"payable" => false, |
||||
"outputs" => [%{"type" => "uint256", "name" => ""}], |
||||
"name" => "mintedTotally", |
||||
"inputs" => [], |
||||
"constant" => true |
||||
} |
||||
|
||||
# 553a5c85=keccak256(mintedTotallymintedTotally()) |
||||
@total_minted_coins_params %{"553a5c85" => []} |
||||
|
||||
def market_cap(%{usd_value: usd_value}) when not is_nil(usd_value) do |
||||
total_market_cap_from_token_bridge = token_bridge_market_cap(%{usd_value: usd_value}) |
||||
total_market_cap_from_omni = total_market_cap_from_omni_bridge() |
||||
|
||||
if Decimal.compare(total_market_cap_from_omni, 0) == :gt do |
||||
Decimal.add(total_market_cap_from_token_bridge, total_market_cap_from_omni) |
||||
else |
||||
total_market_cap_from_token_bridge |
||||
end |
||||
end |
||||
|
||||
def market_cap(_) do |
||||
Decimal.new(0) |
||||
end |
||||
|
||||
def token_bridge_market_cap(%{usd_value: usd_value}) when not is_nil(usd_value) do |
||||
total_coins_from_token_bridge = get_total_coins_from_token_bridge() |
||||
|
||||
if total_coins_from_token_bridge do |
||||
Decimal.mult(total_coins_from_token_bridge, usd_value) |
||||
else |
||||
Decimal.new(0) |
||||
end |
||||
end |
||||
|
||||
def token_bridge_market_cap(_), do: Decimal.new(0) |
||||
|
||||
def circulating, do: total_chain_supply() |
||||
|
||||
def total, do: total_chain_supply() |
||||
|
||||
def get_total_coins_from_token_bridge, do: Bridge.fetch_token_bridge_total_supply() |
||||
|
||||
def total_market_cap_from_omni_bridge, do: Bridge.fetch_omni_bridge_market_cap() |
||||
|
||||
def total_chain_supply do |
||||
usd_value = |
||||
case Source.fetch_exchange_rates(Source.CoinGecko) do |
||||
{:ok, [rates]} -> |
||||
rates.usd_value |
||||
|
||||
_ -> |
||||
Decimal.new(1) |
||||
end |
||||
|
||||
total_coins_from_token_bridge = get_total_coins_from_token_bridge() |
||||
total_market_cap_from_omni = total_market_cap_from_omni_bridge() |
||||
|
||||
if Decimal.compare(total_coins_from_token_bridge, 0) == :gt && Decimal.compare(total_market_cap_from_omni, 0) == :gt do |
||||
total_coins_from_omni_bridge = Decimal.div(total_market_cap_from_omni, usd_value) |
||||
Decimal.add(total_coins_from_token_bridge, total_coins_from_omni_bridge) |
||||
else |
||||
total_coins_from_token_bridge |
||||
end |
||||
end |
||||
|
||||
defp burned_coins do |
||||
address = System.get_env("TOKEN_BRIDGE_CONTRACT") || @token_bridge_contract_address |
||||
|
||||
call_contract(address, @total_burned_coins_abi, @total_burned_coins_params) |
||||
end |
||||
|
||||
defp block_reward_contract do |
||||
address = System.get_env("TOKEN_BRIDGE_CONTRACT") || @token_bridge_contract_address |
||||
|
||||
call_contract(address, @block_reward_contract_abi, @block_reward_contract_params) |
||||
end |
||||
|
||||
defp minted_coins do |
||||
address = block_reward_contract() |
||||
|
||||
call_contract(address, @total_minted_coins_abi, @total_minted_coins_params) |
||||
end |
||||
|
||||
defp call_contract(address, abi, params) do |
||||
abi = [abi] |
||||
|
||||
method_id = |
||||
params |
||||
|> Enum.map(fn {key, _value} -> key end) |
||||
|> List.first() |
||||
|
||||
type = |
||||
abi |
||||
|> Enum.at(0) |
||||
|> Map.get("outputs", []) |
||||
|> Enum.at(0) |
||||
|> Map.get("type", "") |
||||
|
||||
value = |
||||
case Reader.query_contract(address, abi, params, false) do |
||||
%{^method_id => {:ok, [result]}} -> |
||||
result |
||||
|
||||
_ -> |
||||
case type do |
||||
"address" -> "0x0000000000000000000000000000000000000000" |
||||
"uint256" -> 0 |
||||
_ -> 0 |
||||
end |
||||
end |
||||
|
||||
case type do |
||||
"address" -> |
||||
value |
||||
|
||||
"uint256" -> |
||||
%Wei{value: Decimal.new(value)} |
||||
|
||||
_ -> |
||||
value |
||||
end |
||||
end |
||||
|
||||
def get_current_total_supply_from_token_bridge do |
||||
minted_coins() |
||||
|> Wei.sub(burned_coins()) |
||||
|> Wei.to(:ether) |
||||
end |
||||
|
||||
def get_current_market_cap_from_omni_bridge do |
||||
bridged_mainnet_tokens_list = get_bridged_mainnet_tokens_list() |
||||
|
||||
bridged_mainnet_tokens_with_supply = |
||||
bridged_mainnet_tokens_list |
||||
|> get_bridged_mainnet_tokens_supply() |
||||
|
||||
omni_bridge_market_cap = calc_omni_bridge_market_cap(bridged_mainnet_tokens_with_supply) |
||||
|
||||
omni_bridge_market_cap |
||||
end |
||||
|
||||
def get_current_price_for_bridged_token(_token_hash, foreign_token_contract_address_hash) |
||||
when is_nil(foreign_token_contract_address_hash), |
||||
do: nil |
||||
|
||||
def get_current_price_for_bridged_token(token_hash, _foreign_token_contract_address_hash) when is_nil(token_hash), |
||||
do: nil |
||||
|
||||
def get_current_price_for_bridged_token(token_hash, foreign_token_contract_address_hash) do |
||||
foreign_token_contract_address_hash_str = |
||||
"0x" <> Base.encode16(foreign_token_contract_address_hash.bytes, case: :lower) |
||||
|
||||
TokenExchangeRateCache.fetch(token_hash, foreign_token_contract_address_hash_str) |
||||
end |
||||
|
||||
def get_bridged_mainnet_tokens_list do |
||||
query = |
||||
from(bt in BridgedToken, |
||||
left_join: t in Token, |
||||
on: t.contract_address_hash == bt.home_token_contract_address_hash, |
||||
where: bt.foreign_chain_id == ^1, |
||||
where: t.bridged == true, |
||||
select: {bt.home_token_contract_address_hash, t.symbol, bt.custom_cap, bt.foreign_token_contract_address_hash}, |
||||
order_by: [desc: t.holder_count] |
||||
) |
||||
|
||||
query |
||||
|> Repo.all() |
||||
end |
||||
|
||||
defp get_bridged_mainnet_tokens_supply(bridged_mainnet_tokens_list) do |
||||
bridged_mainnet_tokens_with_supply = |
||||
bridged_mainnet_tokens_list |
||||
|> Enum.map(fn {bridged_token_hash, _bridged_token_symbol, bridged_token_custom_cap, |
||||
foreign_token_contract_address_hash} -> |
||||
if bridged_token_custom_cap do |
||||
{bridged_token_hash, Decimal.new(0), Decimal.new(0), bridged_token_custom_cap} |
||||
else |
||||
bridged_token_price_from_cache = |
||||
TokenExchangeRateCache.fetch(bridged_token_hash, foreign_token_contract_address_hash) |
||||
|
||||
bridged_token_price = |
||||
if bridged_token_price_from_cache && Decimal.compare(bridged_token_price_from_cache, 0) == :gt do |
||||
bridged_token_price_from_cache |
||||
else |
||||
TokenExchangeRateCache.fetch_token_exchange_rate_by_address(foreign_token_contract_address_hash) |
||||
end |
||||
|
||||
query = |
||||
from(t in Token, |
||||
where: t.contract_address_hash == ^bridged_token_hash, |
||||
select: {t.total_supply, t.decimals} |
||||
) |
||||
|
||||
bridged_token_balance = |
||||
query |
||||
|> Repo.one() |
||||
|
||||
bridged_token_balance_formatted = |
||||
if bridged_token_balance do |
||||
{bridged_token_balance_with_decimals, decimals} = bridged_token_balance |
||||
|
||||
decimals_multiplier = |
||||
10 |
||||
|> :math.pow(Decimal.to_integer(decimals)) |
||||
|> Decimal.from_float() |
||||
|
||||
Decimal.div(bridged_token_balance_with_decimals, decimals_multiplier) |
||||
else |
||||
bridged_token_balance |
||||
end |
||||
|
||||
{bridged_token_hash, bridged_token_price, bridged_token_balance_formatted, nil} |
||||
end |
||||
end) |
||||
|
||||
bridged_mainnet_tokens_with_supply |
||||
end |
||||
|
||||
defp calc_omni_bridge_market_cap(bridged_mainnet_tokens_with_supply) do |
||||
test_token_addresses = CustomContractsHelpers.get_custom_addresses_list(:test_tokens_addresses) |
||||
|
||||
config = Application.get_env(:explorer, Explorer.Counters.Bridge) |
||||
disable_lp_tokens_in_market_cap = Keyword.get(config, :disable_lp_tokens_in_market_cap) |
||||
|
||||
omni_bridge_market_cap = |
||||
bridged_mainnet_tokens_with_supply |
||||
|> Enum.filter(fn {bridged_token_hash, _, _, _} -> |
||||
bridged_token_hash_str = "0x" <> Base.encode16(bridged_token_hash.bytes, case: :lower) |
||||
!Enum.member?(test_token_addresses, bridged_token_hash_str) |
||||
end) |
||||
|> Enum.reduce(Decimal.new(0), fn {_bridged_token_hash, bridged_token_price, bridged_token_balance, |
||||
bridged_token_custom_cap}, |
||||
acc -> |
||||
if !disable_lp_tokens_in_market_cap && bridged_token_custom_cap do |
||||
Decimal.add(acc, bridged_token_custom_cap) |
||||
else |
||||
if bridged_token_price do |
||||
bridged_token_cap = Decimal.mult(bridged_token_price, bridged_token_balance) |
||||
Decimal.add(acc, bridged_token_cap) |
||||
else |
||||
acc |
||||
end |
||||
end |
||||
end) |
||||
|
||||
omni_bridge_market_cap |
||||
end |
||||
end |
@ -1,194 +0,0 @@ |
||||
defmodule Explorer.Counters.Bridge do |
||||
@moduledoc """ |
||||
Caches the total supply of TokenBridge and OmniBridge. |
||||
|
||||
It loads the count asynchronously and in a time interval of 30 minutes. |
||||
""" |
||||
|
||||
use GenServer |
||||
|
||||
alias Explorer.Chain.Cache.TokenExchangeRate |
||||
alias Explorer.Chain.Supply.TokenBridge |
||||
|
||||
@bridges_table :bridges_market_cap |
||||
|
||||
@current_total_supply_from_token_bridge_cache_key "current_total_supply_from_token_bridge" |
||||
@current_market_cap_from_omni_bridge_cache_key "current_market_cap_from_omni_bridge" |
||||
|
||||
@ets_opts [ |
||||
:set, |
||||
:named_table, |
||||
:public, |
||||
read_concurrency: true |
||||
] |
||||
|
||||
# It is undesirable to automatically start the consolidation in all environments. |
||||
# Consider the test environment: if the consolidation initiates but does not |
||||
# finish before a test ends, that test will fail. This way, hundreds of |
||||
# tests were failing before disabling the consolidation and the scheduler in |
||||
# the test env. |
||||
config = Application.get_env(:explorer, Explorer.Counters.Bridge) |
||||
@enable_consolidation Keyword.get(config, :enable_consolidation) |
||||
|
||||
@update_interval_in_seconds Keyword.get(config, :update_interval_in_seconds) |
||||
|
||||
@doc """ |
||||
Starts a process to periodically update bridges marketcaps. |
||||
""" |
||||
@spec start_link(term()) :: GenServer.on_start() |
||||
def start_link(_) do |
||||
GenServer.start_link(__MODULE__, :ok, name: __MODULE__) |
||||
end |
||||
|
||||
@impl true |
||||
def init(_args) do |
||||
create_tables() |
||||
|
||||
{:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}} |
||||
end |
||||
|
||||
def bridges_table_exists? do |
||||
:ets.whereis(@bridges_table) !== :undefined |
||||
end |
||||
|
||||
def create_bridges_table do |
||||
unless bridges_table_exists?() do |
||||
:ets.new(@bridges_table, @ets_opts) |
||||
end |
||||
end |
||||
|
||||
def create_tables do |
||||
TokenExchangeRate.create_cache_table() |
||||
create_bridges_table() |
||||
end |
||||
|
||||
defp schedule_next_consolidation do |
||||
Process.send_after(self(), :consolidate, :timer.seconds(@update_interval_in_seconds)) |
||||
end |
||||
|
||||
@doc """ |
||||
Inserts new bridged token price into the `:ets` table. |
||||
""" |
||||
def insert_price({key, info}) do |
||||
if TokenExchangeRate.cache_table_exists?() do |
||||
TokenExchangeRate.put_into_cache(key, info) |
||||
end |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, %{consolidate?: true} = state) do |
||||
consolidate() |
||||
schedule_next_consolidation() |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_continue(:ok, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
@impl true |
||||
def handle_info(:consolidate, state) do |
||||
consolidate() |
||||
schedule_next_consolidation() |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
# don't handle other messages (e.g. :ssl_closed) |
||||
def handle_info(_, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
def fetch_token_bridge_total_supply do |
||||
if bridges_table_exists?() do |
||||
do_fetch_token_bridge_total_supply(:ets.lookup(@bridges_table, @current_total_supply_from_token_bridge_cache_key)) |
||||
else |
||||
0 |
||||
end |
||||
end |
||||
|
||||
defp do_fetch_token_bridge_total_supply([{_, result}]), do: result |
||||
|
||||
defp do_fetch_token_bridge_total_supply([]) do |
||||
update_total_supply_from_token_bridge_cache() |
||||
end |
||||
|
||||
def fetch_omni_bridge_market_cap do |
||||
if bridges_table_exists?() do |
||||
do_fetch_omni_bridge_market_cap(:ets.lookup(@bridges_table, @current_market_cap_from_omni_bridge_cache_key)) |
||||
else |
||||
Decimal.new(0) |
||||
end |
||||
end |
||||
|
||||
defp do_fetch_omni_bridge_market_cap([{_, result}]), do: result |
||||
|
||||
defp do_fetch_omni_bridge_market_cap([]) do |
||||
update_total_omni_bridge_market_cap_cache() |
||||
end |
||||
|
||||
defp update_total_supply_from_token_bridge_cache do |
||||
if bridges_table_exists?() do |
||||
current_total_supply_from_token_bridge = TokenBridge.get_current_total_supply_from_token_bridge() |
||||
|
||||
:ets.insert( |
||||
@bridges_table, |
||||
{@current_total_supply_from_token_bridge_cache_key, current_total_supply_from_token_bridge} |
||||
) |
||||
|
||||
current_total_supply_from_token_bridge |
||||
else |
||||
0 |
||||
end |
||||
end |
||||
|
||||
defp update_total_omni_bridge_market_cap_cache do |
||||
if bridges_table_exists?() do |
||||
current_total_supply_from_omni_bridge = TokenBridge.get_current_market_cap_from_omni_bridge() |
||||
|
||||
:ets.insert( |
||||
@bridges_table, |
||||
{@current_market_cap_from_omni_bridge_cache_key, current_total_supply_from_omni_bridge} |
||||
) |
||||
|
||||
current_total_supply_from_omni_bridge |
||||
else |
||||
0 |
||||
end |
||||
end |
||||
|
||||
@doc """ |
||||
Consolidates the info by populating the `:ets` table with the current database information. |
||||
""" |
||||
def consolidate do |
||||
bridged_mainnet_tokens_list = TokenBridge.get_bridged_mainnet_tokens_list() |
||||
|
||||
bridged_mainnet_tokens_list |
||||
|> Enum.each(fn {bridged_token_hash, _bridged_token_symbol, _custom_cap, foreign_token_contract_address_hash} -> |
||||
bridged_token_price = |
||||
TokenBridge.get_current_price_for_bridged_token(bridged_token_hash, foreign_token_contract_address_hash) |
||||
|
||||
cache_key = TokenExchangeRate.cache_key(foreign_token_contract_address_hash) |
||||
TokenExchangeRate.put_into_cache(cache_key, bridged_token_price) |
||||
end) |
||||
|
||||
update_total_supply_from_token_bridge_cache() |
||||
update_total_omni_bridge_market_cap_cache() |
||||
end |
||||
|
||||
@doc """ |
||||
Returns a boolean that indicates whether consolidation is enabled |
||||
|
||||
In order to choose whether or not to enable the scheduler and the initial |
||||
consolidation, change the following Explorer config: |
||||
|
||||
`config :explorer, Explorer.Counters.Bridge, enable_consolidation: true` |
||||
|
||||
to: |
||||
|
||||
`config :explorer, Explorer.Counters.Bridge, enable_consolidation: false` |
||||
""" |
||||
def enable_consolidation?, do: @enable_consolidation |
||||
end |
@ -1,71 +0,0 @@ |
||||
defmodule Explorer.ExchangeRates.Source.TokenBridge do |
||||
@moduledoc """ |
||||
Adapter for calculating the market cap and total supply from token bridge |
||||
while still getting other info like price in dollars and bitcoin from a secondary source |
||||
""" |
||||
|
||||
alias Explorer.Chain |
||||
alias Explorer.ExchangeRates.{Source, Token} |
||||
|
||||
import Source, only: [to_decimal: 1] |
||||
|
||||
@behaviour Source |
||||
|
||||
@impl Source |
||||
def format_data(data) do |
||||
data = secondary_source().format_data(data) |
||||
|
||||
token_data = |
||||
data |
||||
|> Enum.find(fn token -> token.symbol == Explorer.coin() end) |
||||
|> build_struct |
||||
|
||||
[token_data] |
||||
end |
||||
|
||||
@impl Source |
||||
def source_url do |
||||
secondary_source().source_url() |
||||
end |
||||
|
||||
@impl Source |
||||
def source_url(_), do: :ignore |
||||
|
||||
@impl Source |
||||
def headers do |
||||
[] |
||||
end |
||||
|
||||
defp build_struct(original_token) do |
||||
%Token{ |
||||
available_supply: to_decimal(Chain.circulating_supply()), |
||||
total_supply: 0, |
||||
btc_value: original_token.btc_value, |
||||
id: original_token.id, |
||||
last_updated: original_token.last_updated, |
||||
market_cap_usd: market_cap_usd(Chain.circulating_supply(), original_token), |
||||
name: original_token.name, |
||||
symbol: original_token.symbol, |
||||
usd_value: original_token.usd_value, |
||||
volume_24h_usd: original_token.volume_24h_usd |
||||
} |
||||
end |
||||
|
||||
defp market_cap_usd(nil, _original_token), do: Decimal.new(0) |
||||
|
||||
defp market_cap_usd(supply, original_token) do |
||||
supply |
||||
|> to_decimal() |
||||
|> Decimal.mult(original_token.usd_value) |
||||
end |
||||
|
||||
@spec secondary_source() :: module() |
||||
defp secondary_source do |
||||
config(:secondary_source) || Explorer.ExchangeRates.Source.CoinGecko |
||||
end |
||||
|
||||
@spec config(atom()) :: term |
||||
defp config(key) do |
||||
Application.get_env(:explorer, __MODULE__, [])[key] |
||||
end |
||||
end |
@ -0,0 +1,11 @@ |
||||
defmodule Explorer.Repo.Migrations.RemoveBridgedTokens do |
||||
use Ecto.Migration |
||||
|
||||
def change do |
||||
drop_if_exists(table(:bridged_tokens)) |
||||
|
||||
alter table(:tokens) do |
||||
remove(:bridged) |
||||
end |
||||
end |
||||
end |
@ -1,67 +0,0 @@ |
||||
defmodule Explorer.Chain.Supply.TokenBridgeTest do |
||||
use EthereumJSONRPC.Case, async: false |
||||
|
||||
import Mox |
||||
|
||||
# alias Explorer.Chain.Supply.TokenBridge |
||||
|
||||
@moduletag :capture_log |
||||
|
||||
setup :set_mox_global |
||||
|
||||
setup :verify_on_exit! |
||||
|
||||
describe "total_coins/1" do |
||||
@tag :no_parity |
||||
@tag :no_geth |
||||
# Flaky test |
||||
# test "calculates total coins", %{json_rpc_named_arguments: json_rpc_named_arguments} do |
||||
# if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do |
||||
# EthereumJSONRPC.Mox |
||||
# |> expect(:json_rpc, fn [ |
||||
# %{ |
||||
# id: id, |
||||
# method: "eth_call", |
||||
# params: [ |
||||
# %{data: "0x553a5c85", to: "0x867305d19606aadba405ce534e303d0e225f9556"}, |
||||
# "latest" |
||||
# ] |
||||
# } |
||||
# ], |
||||
# _options -> |
||||
# {:ok, |
||||
# [ |
||||
# %{ |
||||
# id: id, |
||||
# jsonrpc: "2.0", |
||||
# result: "0x00000000000000000000000000000000000000000000042aa8fe57ebb112dcc8" |
||||
# } |
||||
# ]} |
||||
# end) |
||||
# |> expect(:json_rpc, fn [ |
||||
# %{ |
||||
# id: id, |
||||
# jsonrpc: "2.0", |
||||
# method: "eth_call", |
||||
# params: [ |
||||
# %{data: "0x0e8162ba", to: "0x7301CFA0e1756B71869E93d4e4Dca5c7d0eb0AA6"}, |
||||
# "latest" |
||||
# ] |
||||
# } |
||||
# ], |
||||
# _options -> |
||||
# {:ok, |
||||
# [ |
||||
# %{ |
||||
# id: id, |
||||
# jsonrpc: "2.0", |
||||
# result: "0x00000000000000000000000000000000000000000000033cc192839185166fc6" |
||||
# } |
||||
# ]} |
||||
# end) |
||||
# end |
||||
|
||||
# assert Decimal.round(TokenBridge.total_coins(), 2, :down) == Decimal.from_float(4388.55) |
||||
# end |
||||
end |
||||
end |
@ -1,45 +0,0 @@ |
||||
defmodule Explorer.ExchangeRates.Source.TokenBridgeTest do |
||||
use Explorer.DataCase |
||||
|
||||
alias Explorer.ExchangeRates |
||||
alias Explorer.ExchangeRates.Source.CoinGecko |
||||
alias Explorer.ExchangeRates.Source.TokenBridge |
||||
alias Explorer.ExchangeRates.Token |
||||
alias Plug.Conn |
||||
|
||||
@json "#{File.cwd!()}/test/support/fixture/exchange_rates/coin_gecko.json" |
||||
|> File.read!() |
||||
|> Jason.decode!() |
||||
|
||||
@json_btc_price """ |
||||
{ |
||||
"rates": { |
||||
"usd": { |
||||
"name": "US Dollar", |
||||
"unit": "$", |
||||
"value": 6547.418, |
||||
"type": "fiat" |
||||
} |
||||
} |
||||
} |
||||
""" |
||||
|
||||
describe "format_data/1" do |
||||
setup do |
||||
bypass = Bypass.open() |
||||
envs = Application.get_env(:explorer, ExchangeRates) |
||||
Application.put_env(:explorer, ExchangeRates, Keyword.put(envs, :source, "coin_gecko")) |
||||
Application.put_env(:explorer, CoinGecko, base_url: "http://localhost:#{bypass.port}") |
||||
|
||||
{:ok, bypass: bypass} |
||||
end |
||||
|
||||
test "bring a list with one %Token{}", %{bypass: bypass} do |
||||
Bypass.expect(bypass, "GET", "/exchange_rates", fn conn -> |
||||
Conn.resp(conn, 200, @json_btc_price) |
||||
end) |
||||
|
||||
assert [%Token{}] = TokenBridge.format_data(@json) |
||||
end |
||||
end |
||||
end |
@ -1,52 +0,0 @@ |
||||
defmodule Indexer.CalcLpTokensTotalLiqudity do |
||||
@moduledoc """ |
||||
Peiodically updates LP tokens total liquidity |
||||
""" |
||||
|
||||
use GenServer |
||||
|
||||
require Logger |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
@interval :timer.minutes(20) |
||||
|
||||
def start_link([init_opts, gen_server_opts]) do |
||||
start_link(init_opts, gen_server_opts) |
||||
end |
||||
|
||||
def start_link(init_opts, gen_server_opts) do |
||||
GenServer.start_link(__MODULE__, init_opts, gen_server_opts) |
||||
end |
||||
|
||||
@impl GenServer |
||||
def init(opts) do |
||||
interval = opts[:interval] || @interval |
||||
|
||||
Process.send_after(self(), :calc_total_liqudity, interval) |
||||
|
||||
{:ok, %{interval: interval}} |
||||
end |
||||
|
||||
@impl GenServer |
||||
def handle_info(:calc_total_liqudity, %{interval: interval} = state) do |
||||
Logger.debug(fn -> "Calc LP tokens total liquidity" end) |
||||
|
||||
calc_total_liqudity() |
||||
|
||||
Process.send_after(self(), :calc_total_liqudity, interval) |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
# don't handle other messages (e.g. :ssl_closed) |
||||
def handle_info(_, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
defp calc_total_liqudity do |
||||
Chain.calc_lp_tokens_total_liqudity() |
||||
|
||||
Logger.debug(fn -> "Total liqudity fetched for LP tokens" end) |
||||
end |
||||
end |
@ -1,44 +0,0 @@ |
||||
defmodule Indexer.SetAmbBridgedMetadataForTokens do |
||||
@moduledoc """ |
||||
Sets token metadata for bridged tokens from AMB extensions. |
||||
""" |
||||
|
||||
use GenServer |
||||
|
||||
require Logger |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
def start_link([init_opts, gen_server_opts]) do |
||||
start_link(init_opts, gen_server_opts) |
||||
end |
||||
|
||||
def start_link(init_opts, gen_server_opts) do |
||||
GenServer.start_link(__MODULE__, init_opts, gen_server_opts) |
||||
end |
||||
|
||||
@impl GenServer |
||||
def init(_opts) do |
||||
send(self(), :process_amb_tokens) |
||||
|
||||
{:ok, %{}} |
||||
end |
||||
|
||||
@impl GenServer |
||||
def handle_info(:process_amb_tokens, state) do |
||||
fetch_amb_bridged_tokens_metadata() |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
# don't handle other messages (e.g. :ssl_closed) |
||||
def handle_info(_, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
defp fetch_amb_bridged_tokens_metadata do |
||||
:ok = Chain.process_amb_tokens() |
||||
|
||||
Logger.debug(fn -> "Bridged status fetched for AMB tokens" end) |
||||
end |
||||
end |
@ -1,54 +0,0 @@ |
||||
defmodule Indexer.SetOmniBridgedMetadataForTokens do |
||||
@moduledoc """ |
||||
Peiodically checks unprocessed tokens and sets bridged status. |
||||
""" |
||||
|
||||
use GenServer |
||||
|
||||
require Logger |
||||
|
||||
alias Explorer.Chain |
||||
|
||||
@interval :timer.minutes(10) |
||||
|
||||
def start_link([init_opts, gen_server_opts]) do |
||||
start_link(init_opts, gen_server_opts) |
||||
end |
||||
|
||||
def start_link(init_opts, gen_server_opts) do |
||||
GenServer.start_link(__MODULE__, init_opts, gen_server_opts) |
||||
end |
||||
|
||||
@impl GenServer |
||||
def init(opts) do |
||||
interval = opts[:interval] || @interval |
||||
|
||||
Process.send_after(self(), :reveal_unprocessed_tokens, interval) |
||||
|
||||
{:ok, %{interval: interval}} |
||||
end |
||||
|
||||
@impl GenServer |
||||
def handle_info(:reveal_unprocessed_tokens, %{interval: interval} = state) do |
||||
Logger.debug(fn -> "Reveal unprocessed tokens" end) |
||||
|
||||
{:ok, token_addresses} = Chain.unprocessed_token_addresses_to_reveal_bridged_tokens() |
||||
|
||||
fetch_omni_bridged_tokens_metadata(token_addresses) |
||||
|
||||
Process.send_after(self(), :reveal_unprocessed_tokens, interval) |
||||
|
||||
{:noreply, state} |
||||
end |
||||
|
||||
# don't handle other messages (e.g. :ssl_closed) |
||||
def handle_info(_, state) do |
||||
{:noreply, state} |
||||
end |
||||
|
||||
defp fetch_omni_bridged_tokens_metadata(token_addresses) do |
||||
:ok = Chain.fetch_omni_bridged_tokens_metadata(token_addresses) |
||||
|
||||
Logger.debug(fn -> "Bridged status fetched for tokens" end) |
||||
end |
||||
end |
Loading…
Reference in new issue