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...") %> +
+ + +
+ +
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,