Merge pull request #7097 from blockscout/vb-force-display-token-instance-page

Force display token instance page
pull/7119/head
Victor Baranov 2 years ago committed by GitHub
commit d3758c0d7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 33
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex
  3. 21
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/helper.ex
  4. 24
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/holder_controller.ex
  5. 15
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/metadata_controller.ex
  6. 24
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
  7. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance_controller.ex
  8. 3
      apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs
  9. 6
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs
  10. 2
      apps/block_scout_web/test/block_scout_web/controllers/tokens/instance/transfer_controller_test.exs
  11. 2
      apps/block_scout_web/test/block_scout_web/controllers/tokens/instance_controller_test.exs
  12. 8
      apps/block_scout_web/test/block_scout_web/views/nft_helpers_test.exs
  13. 19
      apps/explorer/lib/explorer/chain.ex
  14. 4
      apps/explorer/lib/explorer/chain/token_transfer.ex

@ -26,6 +26,7 @@
- [#7144](https://github.com/blockscout/blockscout/pull/7144) - Update Blockscout logo
- [#7136](https://github.com/blockscout/blockscout/pull/7136) - Add release link or commit hash to docker images
- [#7097](https://github.com/blockscout/blockscout/pull/7097) - Force display token instance page
- [#7072](https://github.com/blockscout/blockscout/pull/7072) - Add a separate docker compose for geth with clique consensus
- [#7056](https://github.com/blockscout/blockscout/pull/7056) - Add path_helper in interact.js
- [#7040](https://github.com/blockscout/blockscout/pull/7040) - Use alias BlockScoutWeb.Cldr.Number

@ -110,29 +110,33 @@ defmodule BlockScoutWeb.API.V2.TokenController do
end
end
def instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id} = params) do
def instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
{:not_found, {:ok, token_instance}} <-
{:not_found,
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true)} do
{:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)},
{:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do
token_instance =
case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do
{:ok, token_instance} -> token_instance |> Chain.put_owner_to_token_instance(@api_true)
{:error, :not_found} -> %{token_id: token_id, metadata: nil, owner: nil}
end
conn
|> put_status(200)
|> render(:token_instance, %{
token_instance: token_instance |> Chain.put_owner_to_token_instance(@api_true),
token_instance: token_instance,
token: token
})
end
end
def transfers_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id} = params) do
def transfers_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, _token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
{:not_found, {:ok, _token_instance}} <-
{:not_found,
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true)} do
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
{:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)},
{:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do
paging_options = paging_options(params)
results =
@ -155,13 +159,12 @@ defmodule BlockScoutWeb.API.V2.TokenController do
end
end
def transfers_count_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id} = params) do
def transfers_count_by_instance(conn, %{"address_hash" => address_hash_string, "token_id" => token_id_str} = params) do
with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)},
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params),
{:not_found, {:ok, _token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
{:not_found, {:ok, _token_instance}} <-
{:not_found,
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true)} do
{:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)},
{:not_found, false} <- {:not_found, Chain.is_erc_20_token?(token)},
{:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do
conn
|> put_status(200)
|> json(%{

@ -0,0 +1,21 @@
defmodule BlockScoutWeb.Tokens.Instance.Helper do
@moduledoc """
Token instance controllers common helper
"""
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias Explorer.Chain
def render(conn, token_instance, hash, token_id, token) do
render(
conn,
"index.html",
token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)},
current_path: Controller.current_full_path(conn),
token: token,
total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id)
)
end
end

@ -1,17 +1,19 @@
defmodule BlockScoutWeb.Tokens.Instance.HolderController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias BlockScoutWeb.Tokens.HolderView
alias BlockScoutWeb.Tokens.Instance.Helper
alias Explorer.Chain
alias Explorer.Chain.Address
alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id, "type" => "JSON"} = params) do
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(token_address_hash),
{:ok, token} <- Chain.token_from_address_hash(address_hash),
false <- Chain.is_erc_20_token?(token),
{token_id, ""} <- Integer.parse(token_id_str),
token_holders <-
Chain.fetch_token_holders_from_token_hash_and_token_id(address_hash, token_id, paging_options(params)) do
{token_holders_paginated, next_page} = split_list_by_page(token_holders)
@ -51,21 +53,17 @@ defmodule BlockScoutWeb.Tokens.Instance.HolderController do
end
end
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
{:ok, token} <- Chain.token_from_address_hash(hash, options),
{:ok, token_instance} <-
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do
render(
conn,
"index.html",
token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)},
current_path: Controller.current_full_path(conn),
token: token,
total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id)
)
false <- Chain.is_erc_20_token?(token),
{token_id, ""} <- Integer.parse(token_id_str) do
case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do
{:ok, token_instance} -> Helper.render(conn, token_instance, hash, token_id, token)
{:error, :not_found} -> Helper.render(conn, nil, hash, token_id, token)
end
else
_ ->
not_found(conn)

@ -1,25 +1,20 @@
defmodule BlockScoutWeb.Tokens.Instance.MetadataController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias BlockScoutWeb.Tokens.Instance.Helper
alias Explorer.Chain
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
{:ok, token} <- Chain.token_from_address_hash(hash, options),
false <- Chain.is_erc_20_token?(token),
{token_id, ""} <- Integer.parse(token_id_str),
{:ok, token_instance} <-
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do
if token_instance.metadata do
render(
conn,
"index.html",
token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)},
current_path: Controller.current_full_path(conn),
token: token,
total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id)
)
Helper.render(conn, token_instance, hash, token_id, token)
else
not_found(conn)
end

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.Tokens.Instance.TransferController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.Controller
alias BlockScoutWeb.Tokens.Instance.Helper
alias BlockScoutWeb.Tokens.TransferView
alias Explorer.Chain
alias Explorer.Chain.Address
@ -12,9 +12,11 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
@burn_address_hash burn_address_hash
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id, "type" => "JSON"} = params) do
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
{:ok, token} <- Chain.token_from_address_hash(hash),
false <- Chain.is_erc_20_token?(token),
{token_id, ""} <- Integer.parse(token_id_str),
token_transfers <-
Chain.fetch_token_transfers_from_token_hash_and_token_id(hash, token_id, paging_options(params)) do
{token_transfers_paginated, next_page} = split_list_by_page(token_transfers)
@ -53,21 +55,17 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
end
end
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id_str}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
{:ok, token} <- Chain.token_from_address_hash(hash, options),
{:ok, token_instance} <-
Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do
render(
conn,
"index.html",
token_instance: %{instance: token_instance, token_id: Decimal.new(token_id)},
current_path: Controller.current_full_path(conn),
token: token,
total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id)
)
false <- Chain.is_erc_20_token?(token),
{token_id, ""} <- Integer.parse(token_id_str) do
case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, hash) do
{:ok, token_instance} -> Helper.render(conn, token_instance, hash, token_id, token)
{:error, :not_found} -> Helper.render(conn, nil, hash, token_id, token)
end
else
_ ->
not_found(conn)

@ -6,8 +6,8 @@ defmodule BlockScoutWeb.Tokens.InstanceController do
def show(conn, %{"token_id" => token_address_hash, "id" => token_id}) do
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash),
:ok <- Chain.check_token_exists(hash),
:ok <- Chain.check_erc721_or_erc1155_token_instance_exists(token_id, hash) do
{:ok, token} <- Chain.token_from_address_hash(hash),
false <- Chain.is_erc_20_token?(token) do
token_instance_transfer_path =
conn
|> token_instance_transfer_path(:index, token_address_hash, token_id)

@ -133,9 +133,8 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
test "returns next page of results based on last seen token for erc-1155", %{conn: conn} do
address = insert(:address)
tokens =
1..51
|> Enum.reduce([], fn i, acc ->
|> Enum.reduce([], fn _i, acc ->
token = insert(:token, name: "FN2 Token", type: "ERC-1155")
insert(

@ -489,7 +489,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
end
test "get token instance by token id", %{conn: conn} do
token = insert(:token)
token = insert(:token, type: "ERC-721")
for _ <- 0..50 do
insert(:token_instance, token_id: 0)
@ -713,7 +713,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
end
test "receive 0 count", %{conn: conn} do
token = insert(:token)
token = insert(:token, type: "ERC-721")
insert(:token_instance, token_id: 0, token_contract_address_hash: token.contract_address_hash)
@ -723,7 +723,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do
end
test "get count > 0", %{conn: conn} do
token = insert(:token)
token = insert(:token, type: "ERC-721")
for _ <- 0..50 do
insert(:token_instance, token_id: 0)

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferControllerTest do
describe "GET token-transfers/2" do
test "fetches the instance", %{conn: conn} do
token = insert(:token)
token = insert(:token, type: "ERC-721")
contract_address_hash = token.contract_address_hash

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.InstanceControllerTest do
describe "GET show/2" do
test "redirects with valid params", %{conn: conn} do
token = insert(:token)
token = insert(:token, type: "ERC-721")
contract_address_hash = token.contract_address_hash

@ -1,28 +1,28 @@
defmodule BlockScoutWeb.NFTHelpersTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.{NFTHelpers}
alias BlockScoutWeb.NFTHelpers
describe "compose_ipfs_url/1" do
test "transforms ipfs link like ipfs://${id}" do
url = "ipfs://QmYFf7D2UtqnNz8Lu57Gnk3dxgdAiuboPWMEaNNjhr29tS/hidden.png"
assert "https://ipfs.io/ipfs/QmYFf7D2UtqnNz8Lu57Gnk3dxgdAiuboPWMEaNNjhr29tS/hidden.png" ==
BlockScoutWeb.NFTHelpers.compose_ipfs_url(url)
NFTHelpers.compose_ipfs_url(url)
end
test "transforms ipfs link like ipfs://ipfs" do
url = "ipfs://ipfs/Qmbgk4Ps5kiVdeYCHufMFgqzWLFuovFRtenY5P8m9vr9XW/animation.mp4"
assert "https://ipfs.io/ipfs/Qmbgk4Ps5kiVdeYCHufMFgqzWLFuovFRtenY5P8m9vr9XW/animation.mp4" ==
BlockScoutWeb.NFTHelpers.compose_ipfs_url(url)
NFTHelpers.compose_ipfs_url(url)
end
test "transforms ipfs link in different case" do
url = "IpFs://baFybeid4ed2ua7fwupv4nx2ziczr3edhygl7ws3yx6y2juon7xakgj6cfm/51.json"
assert "https://ipfs.io/ipfs/baFybeid4ed2ua7fwupv4nx2ziczr3edhygl7ws3yx6y2juon7xakgj6cfm/51.json" ==
BlockScoutWeb.NFTHelpers.compose_ipfs_url(url)
NFTHelpers.compose_ipfs_url(url)
end
end
end

@ -5042,7 +5042,7 @@ defmodule Explorer.Chain do
TokenTransfer.fetch_token_transfers_from_token_hash(token_address_hash, options)
end
@spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), binary(), [paging_options]) :: []
@spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [paging_options]) :: []
def fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options \\ []) do
TokenTransfer.fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options)
end
@ -5052,7 +5052,7 @@ defmodule Explorer.Chain do
TokenTransfer.count_token_transfers_from_token_hash(token_address_hash)
end
@spec count_token_transfers_from_token_hash_and_token_id(Hash.t(), binary(), [api?]) :: non_neg_integer()
@spec count_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [api?]) :: non_neg_integer()
def count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options \\ []) do
TokenTransfer.count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options)
end
@ -5233,7 +5233,7 @@ defmodule Explorer.Chain do
|> select_repo(options).all()
end
@spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t(), [api?]) ::
@spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(non_neg_integer(), Hash.Address.t(), [api?]) ::
{:ok, Instance.t()} | {:error, :not_found}
def erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, token_contract_address, options \\ []) do
query =
@ -5649,6 +5649,7 @@ defmodule Explorer.Chain do
|> TypeDecoder.decode_raw(types)
end
@spec get_token_type(Hash.Address.t()) :: String.t() | nil
def get_token_type(hash) do
query =
from(
@ -5660,6 +5661,18 @@ defmodule Explorer.Chain do
Repo.one(query)
end
@spec is_erc_20_token?(Token.t()) :: bool
def is_erc_20_token?(token) do
is_erc_20_token_type?(token.type)
end
defp is_erc_20_token_type?(type) do
case type do
"ERC-20" -> true
_ -> false
end
end
@doc """
Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists.

@ -173,7 +173,7 @@ defmodule Explorer.Chain.TokenTransfer do
|> Chain.select_repo(options).all()
end
@spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), binary(), [paging_options | api?]) :: []
@spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [paging_options | api?]) :: []
def fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -205,7 +205,7 @@ defmodule Explorer.Chain.TokenTransfer do
Repo.one(query, timeout: :infinity)
end
@spec count_token_transfers_from_token_hash_and_token_id(Hash.t(), binary(), [api?]) :: non_neg_integer()
@spec count_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [api?]) :: non_neg_integer()
def count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options) do
query =
from(

Loading…
Cancel
Save