Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force display token instance page #7097

Merged
merged 3 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
### Chore

- [#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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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(%{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,19 @@ 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 ->
token = insert(:token, name: "FN2 Token", type: "ERC-1155")
1..51
|> Enum.reduce([], fn _i, acc ->
token = insert(:token, name: "FN2 Token", type: "ERC-1155")

insert(
:address_current_token_balance,
token_contract_address_hash: token.contract_address_hash,
address: address,
value: 3
)
insert(
:address_current_token_balance,
token_contract_address_hash: token.contract_address_hash,
address: address,
value: 3
)

acc ++ [token.name]
end)
acc ++ [token.name]
end)

conn =
get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), %{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
19 changes: 16 additions & 3 deletions apps/explorer/lib/explorer/chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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(
Expand All @@ -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.

Expand Down
Loading