diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
index 15a757065f..a0e96df02a 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
@@ -1,8 +1,10 @@
defmodule BlockScoutWeb.AddressTokenTransferController do
use BlockScoutWeb, :controller
- alias Explorer.{Chain, Market}
+ alias BlockScoutWeb.TransactionView
alias Explorer.ExchangeRates.Token
+ alias Explorer.{Chain, Market}
+ alias Phoenix.View
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
@@ -11,12 +13,16 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
def index(
conn,
- %{"address_id" => address_hash_string, "address_token_id" => token_hash_string} = params
+ %{
+ "address_id" => address_hash_string,
+ "address_token_id" => token_hash_string,
+ "type" => "JSON"
+ } = params
) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash),
- {:ok, token} <- Chain.token_from_address_hash(token_hash) do
+ {:ok, _} <- Chain.token_from_address_hash(token_hash) do
transactions =
Chain.address_to_transactions_with_token_transfers(
address_hash,
@@ -26,15 +32,58 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
{transactions_paginated, next_page} = split_list_by_page(transactions)
+ next_page_path =
+ case next_page_params(next_page, transactions_paginated, params) do
+ nil ->
+ nil
+
+ next_page_params ->
+ address_token_transfers_path(
+ conn,
+ :index,
+ address_hash_string,
+ token_hash_string,
+ Map.delete(next_page_params, "type")
+ )
+ end
+
+ transfers_json =
+ Enum.map(transactions_paginated, fn transaction ->
+ View.render_to_string(
+ TransactionView,
+ "_tile.html",
+ conn: conn,
+ transaction: transaction,
+ current_address: address
+ )
+ end)
+
+ json(conn, %{items: transfers_json, next_page_path: next_page_path})
+ else
+ :error ->
+ unprocessable_entity(conn)
+
+ {:error, :not_found} ->
+ not_found(conn)
+ end
+ end
+
+ def index(
+ conn,
+ %{"address_id" => address_hash_string, "address_token_id" => token_hash_string}
+ ) do
+ with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
+ {:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string),
+ {:ok, address} <- Chain.hash_to_address(address_hash),
+ {:ok, token} <- Chain.token_from_address_hash(token_hash) do
render(
conn,
"index.html",
address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- next_page_params: next_page_params(next_page, transactions_paginated, params),
+ current_path: current_path(conn),
token: token,
transaction_count: transaction_count(address),
- transactions: transactions_paginated,
validation_count: validation_count(address)
)
else
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
index f6334637bf..ba10748ee1 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
@@ -7,41 +7,36 @@
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
-
+
<%= gettext "Tokens" %> / <%= token_name(@token) %>
-
- <%= if Enum.any?(@transactions) do %>
-
- <%= for transaction <- @transactions do %>
- <%= render(
- BlockScoutWeb.TransactionView,
- "_tile.html",
- transaction: transaction,
- current_address: @address
- ) %>
- <% end %>
+
+
+
+
- <% else %>
-
- <%= gettext "There are no token transfers for this address." %>
-
- <% end %>
-
- <%= if @next_page_params do %>
- <%= link(
- gettext("Next"),
- class: "button button-secondary button-sm float-right mt-3",
- to: address_token_transfers_path(
- @conn,
- :index,
- @address.hash,
- @token.contract_address_hash,
- @next_page_params
- )
- ) %>
- <% end %>
+ <%= gettext("Loading...") %>
+
+
+ <%= gettext "There are no token transfers for this address." %>
+
+
+
+
+ <%= gettext("Older") %>
+
+
+
+
+
+
+ <%= gettext("Loading") %>...
+
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index d84cfc3089..6734912e56 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -610,7 +610,6 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:25
-#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34
msgid "Next"
msgstr ""
@@ -639,6 +638,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:31
#: lib/block_scout_web/templates/address_validation/index.html.eex:117
#: lib/block_scout_web/templates/block/index.html.eex:30
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@@ -831,7 +831,7 @@ msgid "There are no logs for this transaction."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:22
msgid "There are no token transfers for this address."
msgstr ""
@@ -1187,6 +1187,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
msgid "Loading..."
msgstr ""
@@ -1216,6 +1217,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:38
#: lib/block_scout_web/templates/address_transaction/index.html.eex:61
#: lib/block_scout_web/templates/block/index.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:33
@@ -1412,6 +1414,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:26
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
msgid "Something went wrong, click to reload."
msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index 25fb7ce361..105c66ff83 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -610,7 +610,6 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:25
-#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34
msgid "Next"
msgstr ""
@@ -639,6 +638,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:76
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:31
#: lib/block_scout_web/templates/address_validation/index.html.eex:117
#: lib/block_scout_web/templates/block/index.html.eex:30
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@@ -831,7 +831,7 @@ msgid "There are no logs for this transaction."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:22
msgid "There are no token transfers for this address."
msgstr ""
@@ -1187,6 +1187,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:25
msgid "Loading..."
msgstr ""
@@ -1216,6 +1217,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:38
#: lib/block_scout_web/templates/address_transaction/index.html.eex:61
#: lib/block_scout_web/templates/block/index.html.eex:22
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:33
@@ -1412,6 +1414,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
+#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:26
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
msgid "Something went wrong, click to reload."
msgstr ""
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs
index aeb32b06ac..d064eae688 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs
@@ -1,7 +1,8 @@
defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
use BlockScoutWeb.ConnCase
- import BlockScoutWeb.Router.Helpers, only: [address_token_transfers_path: 4]
+ import BlockScoutWeb.Router.Helpers,
+ only: [address_token_transfers_path: 4, address_token_transfers_path: 5]
alias Explorer.Chain.{Address, Token}
@@ -15,6 +16,7 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
test "with invalid token hash", %{conn: conn} do
address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
+
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address"))
assert html_response(conn, 422)
@@ -35,25 +37,80 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
assert html_response(conn, 404)
end
+ end
+ describe "GET index/2 JSON" do
test "without token transfers for a token", %{conn: conn} do
%Address{hash: address_hash} = insert(:address)
%Token{contract_address_hash: token_hash} = insert(:token)
- conn = get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash))
+ conn =
+ get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{
+ type: "JSON"
+ })
+
+ assert json_response(conn, 200) == %{"items" => [], "next_page_path" => nil}
+ end
+
+ test "returns the correct number of transactions", %{conn: conn} do
+ address = insert(:address)
+ token = insert(:token)
+
+ inserted_transactions =
+ Enum.map(1..5, fn index ->
+ block = insert(:block, number: 1000 - index)
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(block)
+
+ insert(
+ :token_transfer,
+ to_address: address,
+ transaction: transaction,
+ token_contract_address: token.contract_address
+ )
+
+ transaction
+ end)
- assert html_response(conn, 200)
- assert conn.assigns.transactions == []
+ conn =
+ get(
+ conn,
+ address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash),
+ %{type: "JSON"}
+ )
+
+ response_items =
+ conn
+ |> json_response(200)
+ |> Map.get("items")
+
+ items_length = length(response_items)
+
+ assert items_length == 5
+
+ assert Enum.all?(inserted_transactions, fn transaction ->
+ Enum.any?(response_items, fn item ->
+ String.contains?(
+ item,
+ "data-transaction-hash=\"#{to_string(transaction.hash)}\""
+ )
+ end)
+ end)
end
- test "returns the transactions that have token transfers for the given address and token", %{conn: conn} do
+ test "returns next_page_path as null when there are no more pages", %{conn: conn} do
address = insert(:address)
token = insert(:token)
+ block = insert(:block, number: 1000)
+
transaction =
:transaction
|> insert()
- |> with_block()
+ |> with_block(block)
insert(
:token_transfer,
@@ -62,19 +119,21 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
token_contract_address: token.contract_address
)
- conn = get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash))
-
- transaction_hashes = Enum.map(conn.assigns.transactions, & &1.hash)
+ conn =
+ get(
+ conn,
+ address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash),
+ %{type: "JSON"}
+ )
- assert html_response(conn, 200)
- assert transaction_hashes == [transaction.hash]
+ assert Map.get(json_response(conn, 200), "next_page_path") == nil
end
- test "returns next page of results based on last seen transactions", %{conn: conn} do
+ test "returns next_page_path when there are more items", %{conn: conn} do
address = insert(:address)
token = insert(:token)
- second_page_transactions =
+ page_last_transfer =
1..50
|> Enum.map(fn index ->
block = insert(:block, number: 1000 - index)
@@ -93,22 +152,84 @@ defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
transaction
end)
- |> Enum.map(& &1.hash)
+ |> List.last()
- transaction =
- :transaction
- |> insert()
- |> with_block(insert(:block, number: 1002))
+ Enum.each(51..60, fn index ->
+ block = insert(:block, number: 1000 - index)
+
+ transaction =
+ :transaction
+ |> insert()
+ |> with_block(block)
+
+ insert(
+ :token_transfer,
+ to_address: address,
+ transaction: transaction,
+ token_contract_address: token.contract_address
+ )
+ end)
conn =
- get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash), %{
- "block_number" => Integer.to_string(transaction.block_number),
- "index" => Integer.to_string(transaction.index)
+ get(
+ conn,
+ address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash),
+ %{type: "JSON"}
+ )
+
+ expected_path =
+ address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash, %{
+ block_number: page_last_transfer.block_number,
+ index: page_last_transfer.index
})
- actual_transactions = Enum.map(conn.assigns.transactions, & &1.hash)
+ assert Map.get(json_response(conn, 200), "next_page_path") == expected_path
+ end
+
+ test "with invalid address hash", %{conn: conn} do
+ token_hash = "0xc8982771dd50285389c352c175ada74d074427c7"
+
+ conn =
+ get(conn, address_token_transfers_path(conn, :index, "invalid_address", token_hash), %{
+ type: "JSON"
+ })
+
+ assert html_response(conn, 422)
+ end
+
+ test "with invalid token hash", %{conn: conn} do
+ address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
+
+ conn =
+ get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address"), %{
+ type: "JSON"
+ })
+
+ assert html_response(conn, 422)
+ end
+
+ test "with an address that doesn't exist in our database", %{conn: conn} do
+ address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
+ %Token{contract_address_hash: token_hash} = insert(:token)
+
+ conn =
+ get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{
+ type: "JSON"
+ })
+
+ assert html_response(conn, 404)
+ end
+
+ test "with a token that doesn't exist in our database", %{conn: conn} do
+ %Address{hash: address_hash} = insert(:address)
+ token_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
- assert second_page_transactions == actual_transactions
+ conn =
+ get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash), %{
+ type: "JSON"
+ })
+
+ assert html_response(conn, 404)
end
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
index db1e081c71..58e4682114 100644
--- a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
@@ -364,6 +364,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
end
describe "viewing token transfers from a specific token" do
+ @tag :skip
test "list token transfers related to the address", %{
addresses: addresses,
block: block,