diff --git a/CHANGELOG.md b/CHANGELOG.md
index e9dfddb009..2bec02ebbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## Current
### Features
+- [#3224](https://github.com/poanetwork/blockscout/pull/3224) - Top tokens page
### Fixes
- [#3220](https://github.com/poanetwork/blockscout/pull/3220) - Allow interaction with navbar menu at block-not-found page
diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss
index d97f0483d5..a9a1442166 100644
--- a/apps/block_scout_web/assets/css/components/_navbar.scss
+++ b/apps/block_scout_web/assets/css/components/_navbar.scss
@@ -155,7 +155,7 @@ $navbar-logo-width: auto !default;
width: 340px;
}
@media (min-width: 1366px) {
- width: 500px;
+ width: 380px;
}
@media (min-width: 1440px) {
width: 580px;
diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex
index 3aa660fbe4..23efee7c7a 100644
--- a/apps/block_scout_web/lib/block_scout_web/chain.ex
+++ b/apps/block_scout_web/lib/block_scout_web/chain.ex
@@ -24,6 +24,7 @@ defmodule BlockScoutWeb.Chain do
Block,
InternalTransaction,
Log,
+ Token,
TokenTransfer,
Transaction,
Wei
@@ -107,6 +108,16 @@ defmodule BlockScoutWeb.Chain do
end
end
+ def paging_options(%{"contract_address_hash" => contract_address_hash, "holder_count" => holder_count}) do
+ with {holder_count, ""} <- Integer.parse(holder_count),
+ {:ok, contract_address_hash} <- string_to_address_hash(contract_address_hash) do
+ [paging_options: %{@default_paging_options | key: {holder_count, contract_address_hash}}]
+ else
+ _ ->
+ [paging_options: @default_paging_options]
+ end
+ end
+
def paging_options(%{
"block_number" => block_number_string,
"transaction_index" => transaction_index_string,
@@ -205,6 +216,10 @@ defmodule BlockScoutWeb.Chain do
%{"hash" => hash, "fetched_coin_balance" => Decimal.to_string(fetched_coin_balance.value)}
end
+ defp paging_params(%Token{contract_address_hash: contract_address_hash, holder_count: holder_count}) do
+ %{"contract_address_hash" => contract_address_hash, "holder_count" => holder_count}
+ end
+
defp paging_params({%Reward{block: %{number: number}}, _}) do
%{"block_number" => number, "index" => 0}
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex
new file mode 100644
index 0000000000..0a133d9f7f
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex
@@ -0,0 +1,60 @@
+defmodule BlockScoutWeb.TokensController do
+ use BlockScoutWeb, :controller
+
+ import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
+
+ alias BlockScoutWeb.TokensView
+ alias Explorer.Chain
+ alias Phoenix.View
+
+ def index(conn, %{"type" => "JSON"} = params) do
+ tokens =
+ params
+ |> paging_options()
+ |> Chain.list_top_tokens()
+
+ {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 ->
+ tokens_path(
+ conn,
+ :index,
+ Map.delete(next_page_params, "type")
+ )
+ end
+
+ items =
+ tokens_page
+ |> Enum.with_index(1)
+ |> Enum.map(fn {token, index} ->
+ View.render_to_string(
+ TokensView,
+ "_tile.html",
+ token: token,
+ index: index
+ )
+ end)
+
+ json(
+ conn,
+ %{
+ items: items,
+ next_page_path: next_page_path
+ }
+ )
+ end
+
+ def index(conn, _params) do
+ total_supply = Chain.total_supply()
+
+ render(conn, "index.html",
+ current_path: current_path(conn),
+ total_supply: total_supply
+ )
+ end
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex
new file mode 100644
index 0000000000..6d808a1800
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/templates/icons/_tokens_icon.html.eex
@@ -0,0 +1,4 @@
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
index 12575322f6..7698f84521 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
@@ -75,6 +75,14 @@
<%= gettext("Accounts") %>
<% end %>
+
+ <%= link to: tokens_path(@conn, :index), class: "nav-link topnav-nav-link #{tab_status("tokens", @conn.request_path)}" do %>
+
+ <%= render BlockScoutWeb.IconsView, "_tokens_icon.html" %>
+
+ <%= gettext("Tokens") %>
+ <% end %>
+
<% end %>
<%= if Application.get_env(:block_scout_web, BlockScoutWeb.ApiRouter)[:reading_enabled] || Application.get_env(:block_scout_web, :api_url) do %>