Skip to content

Commit

Permalink
API v1 bridgedtokenlist endpoint (blockscout#9506)
Browse files Browse the repository at this point in the history
* bridgedtokenlist API endpoint

* Change spec for bridged_token_usd_cap function

* Fix review comments

* Return bridged_token_usd_cap as string
  • Loading branch information
vbaranov authored Mar 13, 2024
1 parent f5674ff commit 610f622
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 3,491 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

- [#9571](https://github.com/blockscout/blockscout/pull/9571) - Support Optimism Ecotone upgrade by Indexer.Fetcher.Optimism.TxnBatch module
- [#9562](https://github.com/blockscout/blockscout/pull/9562) - Add cancun evm version
- [#9506](https://github.com/blockscout/blockscout/pull/9506) - API v1 bridgedtokenlist endpoint
- [#9260](https://github.com/blockscout/blockscout/pull/9260) - Optimism Delta upgrade support by Indexer.Fetcher.OptimismTxnBatch module
- [#8740](https://github.com/blockscout/blockscout/pull/8740) - Add delay to Indexer.Fetcher.OptimismTxnBatch module initialization

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.TokenController do

alias BlockScoutWeb.API.RPC.Helper
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.BridgedToken

def gettoken(conn, params) do
with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params),
Expand Down Expand Up @@ -50,6 +51,38 @@ defmodule BlockScoutWeb.API.RPC.TokenController do
end
end

if Application.compile_env(:explorer, BridgedToken)[:enabled] do
@api_true [api?: true]
def bridgedtokenlist(conn, params) do
import BlockScoutWeb.PagingHelper,
only: [
chain_ids_filter_options: 1,
tokens_sorting: 1
]

import BlockScoutWeb.Chain,
only: [
paging_options: 1
]

bridged_tokens =
if BridgedToken.enabled?() do
options =
params
|> paging_options()
|> Keyword.merge(chain_ids_filter_options(params))
|> Keyword.merge(tokens_sorting(params))
|> Keyword.merge(@api_true)

"" |> BridgedToken.list_top_bridged_tokens(options)
else
[]
end

render(conn, "bridgedtokenlist.json", %{bridged_tokens: bridged_tokens})
end
end

defp fetch_contractaddress(params) do
{:contractaddress_param, Map.fetch(params, "contractaddress")}
end
Expand Down
126 changes: 122 additions & 4 deletions apps/block_scout_web/lib/block_scout_web/etherscan.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Etherscan do
@moduledoc """
Documentation data for Etherscan-compatible API.
"""
alias Explorer.Chain.BridgedToken

@account_balance_example_value %{
"status" => "1",
Expand Down Expand Up @@ -2009,6 +2010,117 @@ defmodule BlockScoutWeb.Etherscan do
]
}

if Application.compile_env(:explorer, BridgedToken)[:enabled] do
@success_status_type %{
type: "status",
enum: ~s(["1"]),
enum_interpretation: %{"1" => "ok"}
}

@bridged_token_details %{
name: "Bridged Token Detail",
fields: %{
foreignChainId: %{
type: "value",
definition: "Chain ID of the chain where original token exists.",
example: ~s("1")
},
foreignTokenContractAddressHash: @address_hash_type,
homeContractAddressHash: @address_hash_type,
homeDecimals: @token_decimal_type,
homeHolderCount: %{
type: "value",
definition: "Token holders count.",
example: ~s("393")
},
homeName: @token_name_type,
homeSymbol: @token_symbol_type,
homeTotalSupply: %{
type: "value",
definition: "Total supply of the token on the home side (where token was bridged).",
example: ~s("1484374.775044204093387391")
},
homeUsdValue: %{
type: "value",
definition: "Total supply of the token on the home side (where token was bridged) in USD.",
example: ~s("6638727.472651464170990256943")
}
}
}

@token_bridgedtokenlist_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"foreignChainId" => "1",
"foreignTokenContractAddressHash" => "0x0ae055097c6d159879521c384f1d2123d1f195e6",
"homeContractAddressHash" => "0xb7d311e2eb55f2f68a9440da38e7989210b9a05e",
"homeDecimals" => "18",
"homeHolderCount" => 393,
"homeName" => "STAKE on xDai",
"homeSymbol" => "STAKE",
"homeTotalSupply" => "1484374.775044204093387391",
"homeUsdValue" => "18807028.39981006586321824397"
},
%{
"foreignChainId" => "1",
"foreignTokenContractAddressHash" => "0xf5581dfefd8fb0e4aec526be659cfab1f8c781da",
"homeContractAddressHash" => "0xd057604a14982fe8d88c5fc25aac3267ea142a08",
"homeDecimals" => "18",
"homeHolderCount" => 73,
"homeName" => "HOPR Token on xDai",
"homeSymbol" => "HOPR",
"homeTotalSupply" => "26600449.86076749062791602",
"homeUsdValue" => "6638727.472651464170990256943"
}
]
}

@token_bridgedtokenlist_action %{
name: "bridgedTokenList",
description: "Get bridged tokens list.",
required_params: [],
optional_params: [
%{
key: "chainid",
type: "integer",
description: "A nonnegative integer that represents the chain id, where original token exists."
},
%{
key: "page",
type: "integer",
description:
"A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction."
},
%{
key: "offset",
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@token_bridgedtokenlist_example_value),
model: %{
name: "Result",
fields: %{
status: @success_status_type,
message: @message_type,
result: %{
type: "array",
array_type: @bridged_token_details
}
}
}
}
]
}
end

@stats_tokensupply_action %{
name: "tokensupply",
description:
Expand Down Expand Up @@ -2947,12 +3059,18 @@ defmodule BlockScoutWeb.Etherscan do
actions: [@logs_getlogs_action]
}

@base_token_actions [
@token_gettoken_action,
@token_gettokenholders_action
]

@token_actions if Application.compile_env(:explorer, BridgedToken)[:enabled],
do: [@token_bridgedtokenlist_action, @base_token_actions],
else: @base_token_actions

@token_module %{
name: "token",
actions: [
@token_gettoken_action,
@token_gettokenholders_action
]
actions: @token_actions
}

@stats_module %{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.RPC.TokenView do
use BlockScoutWeb, :view

alias BlockScoutWeb.API.RPC.RPCView
alias BlockScoutWeb.{BridgedTokensView, CurrencyHelper}

def render("gettoken.json", %{token: token}) do
RPCView.render("show.json", data: prepare_token(token))
Expand All @@ -12,6 +13,11 @@ defmodule BlockScoutWeb.API.RPC.TokenView do
RPCView.render("show.json", data: data)
end

def render("bridgedtokenlist.json", %{bridged_tokens: bridged_tokens}) do
data = Enum.map(bridged_tokens, &prepare_bridged_token/1)
RPCView.render("show.json", data: data)
end

def render("error.json", assigns) do
RPCView.render("error.json", assigns)
end
Expand All @@ -34,4 +40,21 @@ defmodule BlockScoutWeb.API.RPC.TokenView do
"value" => token_holder.value
}
end

defp prepare_bridged_token({token, bridged_token}) do
total_supply = CurrencyHelper.divide_decimals(token.total_supply, token.decimals)
usd_value = BridgedTokensView.bridged_token_usd_cap(bridged_token, token)

%{
"foreignChainId" => bridged_token.foreign_chain_id,
"foreignTokenContractAddressHash" => bridged_token.foreign_token_contract_address_hash,
"homeContractAddressHash" => token.contract_address_hash,
"homeDecimals" => token.decimals,
"homeHolderCount" => if(token.holder_count, do: to_string(token.holder_count), else: "0"),
"homeName" => token.name,
"homeSymbol" => token.symbol,
"homeTotalSupply" => total_supply,
"homeUsdValue" => usd_value
}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule BlockScoutWeb.BridgedTokensView do
use BlockScoutWeb, :view

alias Explorer.Chain.{BridgedToken, CurrencyHelper, Token}

@doc """
Calculates capitalization of the bridged token in USD.
"""
@spec bridged_token_usd_cap(BridgedToken.t(), Token.t()) :: String.t()
def bridged_token_usd_cap(bridged_token, token) do
usd_cap =
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, CurrencyHelper.divide_decimals(token.total_supply, token.decimals))
else
Decimal.new(0)
end
end

usd_cap |> Decimal.to_float() |> :erlang.float_to_binary([:compact, decimals: 20])
end
end
21 changes: 0 additions & 21 deletions apps/explorer/lib/explorer/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3545,27 +3545,6 @@ defmodule Explorer.Chain do
|> Repo.stream_reduce(initial, reducer)
end

def decode_contract_address_hash_response(resp) do
case resp do
"0x000000000000000000000000" <> address ->
"0x" <> address

_ ->
nil
end
end

def decode_contract_integer_response(resp) do
case resp do
"0x" <> integer_encoded ->
{integer_value, _} = Integer.parse(integer_encoded, 16)
integer_value

_ ->
nil
end
end

@doc """
Fetches a `t:Token.t/0` by an address hash.
Expand Down
Loading

0 comments on commit 610f622

Please sign in to comment.