Merge pull request #774 from poanetwork/ams-token-inventory

Contributor sees an Inventory tab for ERC-721 tokens
pull/819/head
Andrew Cravenho 6 years ago committed by GitHub
commit 062172957d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      apps/block_scout_web/assets/css/components/_tile.scss
  2. 4
      apps/block_scout_web/lib/block_scout_web/chain.ex
  3. 52
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  4. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  5. 28
      apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/_token.html.eex
  6. 42
      apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex
  7. 50
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_tabs.html.eex
  8. 5
      apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex
  9. 17
      apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
  10. 9
      apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
  11. 45
      apps/block_scout_web/priv/gettext/default.pot
  12. 45
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  13. 72
      apps/block_scout_web/test/block_scout_web/views/tokens/overview_view_test.exs
  14. 58
      apps/block_scout_web/test/block_scout_web/views/tokens/transfer_view_test.exs
  15. 11
      apps/explorer/lib/explorer/chain.ex
  16. 33
      apps/explorer/lib/explorer/chain/token_transfer.ex
  17. 71
      apps/explorer/test/explorer/chain/token_transfer_test.exs
  18. 43
      apps/explorer/test/explorer/chain_test.exs

@ -63,6 +63,28 @@
}
}
&-unique-token {
border-left: 4px solid $orange;
padding: 35px 0;
.tile-label {
color: $orange;
}
}
&-unique-token-image{
border-left: 4px solid $orange;
padding: 0;
.tile-label {
color: $orange;
}
& .tile-content {
padding: 45px 0;
}
}
&-internal-transaction {
border-left: 4px solid $teal;
@ -150,3 +172,8 @@
margin: 0;
}
}
.tile-image {
max-width: 140px;
max-height: 140px;
}

@ -29,6 +29,10 @@ defmodule BlockScoutWeb.Chain do
@page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1}
def default_paging_options do
@default_paging_options
end
def current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")

@ -0,0 +1,52 @@
defmodule BlockScoutWeb.Tokens.InventoryController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.TokenTransfer
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0]
def index(conn, %{"token_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash) do
unique_tokens =
Chain.address_to_unique_tokens(
token.contract_address_hash,
unique_tokens_paging_options(params)
)
{unique_tokens_paginated, next_page} = split_list_by_page(unique_tokens)
render(
conn,
"index.html",
token: token,
unique_tokens: unique_tokens_paginated,
total_token_transfers: Chain.count_token_transfers_from_token_hash(address_hash),
total_token_holders: Chain.count_token_holders_from_token_hash(address_hash),
next_page_params: unique_tokens_next_page(next_page, unique_tokens_paginated, params)
)
else
:error ->
not_found(conn)
{:error, :not_found} ->
not_found(conn)
end
end
defp unique_tokens_paging_options(%{"unique_token" => token_id}),
do: [paging_options: %{default_paging_options() | key: {token_id}}]
defp unique_tokens_paging_options(_params), do: [paging_options: default_paging_options()]
defp unique_tokens_next_page([], _list, _params), do: nil
defp unique_tokens_next_page(_, list, params) do
Map.merge(params, paging_params(List.last(list)))
end
defp paging_params(%TokenTransfer{token_id: token_id}) do
%{"unique_token" => Decimal.to_integer(token_id)}
end
end

@ -135,6 +135,13 @@ defmodule BlockScoutWeb.Router do
only: [:index],
as: :holder
)
resources(
"/inventory",
Tokens.InventoryController,
only: [:index],
as: :inventory
)
end
resources(

@ -0,0 +1,28 @@
<!-- use "tile-type-unique-token-image" to token with images -->
<div class="tile tile-type-unique-token fade-in">
<div class="row">
<div class="pl-5 col-md-2 d-flex flex-column align-items-left justify-content-start justify-content-lg-center">
<!-- substitute the span with <img class="tile-image" /> to token with images -->
<span class="tile-label"><%= gettext "Unique Token" %></span>
</div>
<div class="tile-content col-md-7 col-lg-8 d-flex flex-column">
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="mr-1"><%= gettext "Token ID" %>:</span>
<span class="tile-title">
<%= @token_transfer.token_id %>
</span>
</span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="mr-1"><%= gettext "Owner Address" %>:</span>
<span class="tile-title">
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @token_transfer.to_address,
contract: false %>
</span>
</span>
</div>
</div>
</div>

@ -0,0 +1,42 @@
<section class="container">
<%= render(
OverviewView,
"_details.html",
token: @token,
total_token_transfers: @total_token_transfers,
total_token_holders: @total_token_holders,
conn: @conn
) %>
<section>
<div class="card">
<div class="card-header">
<%= render OverviewView, "_tabs.html", assigns %>
</div>
<div class="card-body">
<h2 class="card-title"><%= gettext "Inventory" %></h2>
<%= if Enum.any?(@unique_tokens) do %>
<%= for token_transfer <- @unique_tokens do %>
<%= render "_token.html", token_transfer: token_transfer %>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
<span data-selector="empty-transactions-list">
<%= gettext "There are no tokens." %>
</span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Next Page"),
class: "button button-secondary button-small float-right mt-4",
to: token_inventory_path(@conn, :index, @token.contract_address_hash, @next_page_params)
) %>
<% end %>
</div>
</div>
</section>
</section>

@ -8,15 +8,6 @@
) %>
</li>
<%= if TransferView.smart_contract_with_read_only_functions?(@token) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: token_read_contract_path(@conn, :index, @token.contract_address_hash),
class: "nav-link #{tab_status("read_contract", @conn.request_path)}")%>
</li>
<% end %>
<li class="nav-item">
<%= link(
gettext("Token Holders"),
@ -25,6 +16,25 @@
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
<%= if display_inventory?(@token) do %>
<li class="nav-item">
<%= link(
gettext("Inventory"),
class: "nav-link #{tab_status("inventory", @conn.request_path)}",
to: token_inventory_path(@conn, :index, @token.contract_address_hash)
) %>
</li>
<% end %>
<%= if smart_contract_with_read_only_functions?(@token) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: token_read_contract_path(@conn, :index, @token.contract_address_hash),
class: "nav-link #{tab_status("read_contract", @conn.request_path)}")%>
</li>
<% end %>
</ul>
<!-- MOBILE DROPDOWN NAV -->
@ -45,17 +55,27 @@
class: "dropdown-item #{tab_status("token_transfers", @conn.request_path)}",
to: token_path(@conn, :show, @token.contract_address_hash)
) %>
<%= if TransferView.smart_contract_with_read_only_functions?(@token) do %>
<%= link(
gettext("Read Contract"),
to: "#",
class: "dropdown-item #{tab_status("read_contract", @conn.request_path)}")%>
<% end %>
<%= link(
gettext("Token Holders"),
class: "dropdown-item #{tab_status("token_holders", @conn.request_path)}",
to: token_holder_path(@conn, :index, @token.contract_address_hash)
) %>
<%= if display_inventory?(@token) do %>
<%= link(
gettext("Inventory"),
class: "dropdown-item #{tab_status("inventory", @conn.request_path)}",
to: token_inventory_path(@conn, :index, @token.contract_address_hash)
) %>
<% end %>
<%= if smart_contract_with_read_only_functions?(@token) do %>
<%= link(
gettext("Read Contract"),
to: "#",
class: "dropdown-item #{tab_status("read_contract", @conn.request_path)}")%>
<% end %>
</div>
</li>
</ul>

@ -0,0 +1,5 @@
defmodule BlockScoutWeb.Tokens.InventoryView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
end

@ -1,10 +1,9 @@
defmodule BlockScoutWeb.Tokens.OverviewView do
use BlockScoutWeb, :view
alias Explorer.Chain.Token
alias BlockScoutWeb.Tokens.TransferView
alias Explorer.Chain.{Address, SmartContract, Token}
@tabs ["token_transfers", "token_holders", "read_contract"]
@tabs ["token_transfers", "token_holders", "read_contract", "inventory"]
def decimals?(%Token{decimals: nil}), do: false
def decimals?(%Token{decimals: _}), do: true
@ -34,4 +33,16 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
defp tab_name(["token_transfers"]), do: gettext("Token Transfers")
defp tab_name(["token_holders"]), do: gettext("Token Holders")
defp tab_name(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["inventory"]), do: gettext("Inventory")
def display_inventory?(%Token{type: "ERC-721"}), do: true
def display_inventory?(_), do: false
def smart_contract_with_read_only_functions?(
%Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token
) do
Enum.any?(token.contract_address.smart_contract.abi, & &1["constant"])
end
def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
end

@ -1,14 +1,5 @@
defmodule BlockScoutWeb.Tokens.TransferView do
use BlockScoutWeb, :view
alias Explorer.Chain.{Address, SmartContract, Token}
alias BlockScoutWeb.Tokens.OverviewView
def smart_contract_with_read_only_functions?(
%Token{contract_address: %Address{smart_contract: %SmartContract{}}} = token
) do
Enum.any?(token.contract_address.smart_contract.abi, & &1["constant"])
end
def smart_contract_with_read_only_functions?(%Token{contract_address: %Address{smart_contract: nil}}), do: false
end

@ -270,6 +270,7 @@ msgid "TPM"
msgstr ""
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:35
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:34
msgid "Next Page"
msgstr ""
@ -712,9 +713,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:100
#: lib/block_scout_web/templates/address_transaction/index.html.eex:50
#: lib/block_scout_web/templates/address_transaction/index.html.eex:91
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:14
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:50
#: lib/block_scout_web/views/tokens/overview_view.ex:36
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/tokens/overview_view.ex:35
msgid "Read Contract"
msgstr ""
@ -816,12 +817,12 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:44
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:54
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:18
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:6
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:36
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:10
#: lib/block_scout_web/views/tokens/overview_view.ex:34
#: lib/block_scout_web/views/tokens/overview_view.ex:33
#: lib/block_scout_web/views/transaction_view.ex:169
msgid "Token Transfers"
msgstr ""
@ -1090,9 +1091,9 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:19
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:22
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:55
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:60
#: lib/block_scout_web/views/tokens/overview_view.ex:34
msgid "Token Holders"
msgstr ""
@ -1279,3 +1280,31 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:26
msgid "Search tokens"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:18
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:23
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:67
#: lib/block_scout_web/views/tokens/overview_view.ex:36
msgid "Inventory"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18
msgid "Owner Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27
msgid "There are no tokens."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11
msgid "Token ID"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:6
msgid "Unique Token"
msgstr ""

@ -282,6 +282,7 @@ msgid "TPM"
msgstr ""
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:35
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:34
msgid "Next Page"
msgstr ""
@ -724,9 +725,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:100
#: lib/block_scout_web/templates/address_transaction/index.html.eex:50
#: lib/block_scout_web/templates/address_transaction/index.html.eex:91
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:14
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:50
#: lib/block_scout_web/views/tokens/overview_view.ex:36
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75
#: lib/block_scout_web/views/tokens/overview_view.ex:35
msgid "Read Contract"
msgstr ""
@ -828,12 +829,12 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:44
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:54
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:18
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:6
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:36
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:10
#: lib/block_scout_web/views/tokens/overview_view.ex:34
#: lib/block_scout_web/views/tokens/overview_view.ex:33
#: lib/block_scout_web/views/transaction_view.ex:169
msgid "Token Transfers"
msgstr ""
@ -1102,9 +1103,9 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:19
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:22
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:55
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:60
#: lib/block_scout_web/views/tokens/overview_view.ex:34
msgid "Token Holders"
msgstr ""
@ -1291,3 +1292,31 @@ msgstr ""
#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:26
msgid "Search tokens"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:18
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:23
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:67
#: lib/block_scout_web/views/tokens/overview_view.ex:36
msgid "Inventory"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18
msgid "Owner Address"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27
msgid "There are no tokens."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11
msgid "Token ID"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:6
msgid "Unique Token"
msgstr ""

@ -64,4 +64,76 @@ defmodule BlockScoutWeb.Tokens.OverviewViewTest do
assert OverviewView.current_tab_name(read_contract_path) == "Read Contract"
end
end
describe "display_inventory?/1" do
test "returns true when token is unique" do
token = insert(:token, type: "ERC-721")
assert OverviewView.display_inventory?(token) == true
end
test "returns false when token is not unique" do
token = insert(:token, type: "ERC-20")
assert OverviewView.display_inventory?(token) == false
end
end
describe "smart_contract_with_read_only_functions?/1" do
test "returns true when abi has read only functions" do
smart_contract =
insert(
:smart_contract,
abi: [
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
)
address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address)
assert OverviewView.smart_contract_with_read_only_functions?(token)
end
test "returns false when there is no read only functions" do
smart_contract =
insert(
:smart_contract,
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
}
]
)
address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address)
refute OverviewView.smart_contract_with_read_only_functions?(token)
end
test "returns false when smart contract is not verified" do
address = insert(:address, smart_contract: nil)
token = insert(:token, contract_address: address)
refute OverviewView.smart_contract_with_read_only_functions?(token)
end
end
end

@ -2,62 +2,4 @@ defmodule BlockScoutWeb.Tokens.TransferViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.Tokens.TransferView
describe "smart_contract_with_read_only_functions?/1" do
test "returns true when abi has read only functions" do
smart_contract =
insert(
:smart_contract,
abi: [
%{
"constant" => true,
"inputs" => [],
"name" => "get",
"outputs" => [%{"name" => "", "type" => "uint256"}],
"payable" => false,
"stateMutability" => "view",
"type" => "function"
}
]
)
address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address)
assert TransferView.smart_contract_with_read_only_functions?(token)
end
test "returns false when there is no read only functions" do
smart_contract =
insert(
:smart_contract,
abi: [
%{
"constant" => false,
"inputs" => [%{"name" => "x", "type" => "uint256"}],
"name" => "set",
"outputs" => [],
"payable" => false,
"stateMutability" => "nonpayable",
"type" => "function"
}
]
)
address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address)
refute TransferView.smart_contract_with_read_only_functions?(token)
end
test "returns false when smart contract is not verified" do
address = insert(:address, smart_contract: nil)
token = insert(:token, contract_address: address)
refute TransferView.smart_contract_with_read_only_functions?(token)
end
end
end

@ -1868,4 +1868,15 @@ defmodule Explorer.Chain do
|> TokenBalance.token_holders_from_token_hash()
|> Repo.aggregate(:count, :address_hash)
end
@spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [TokenTransfer.t()]
def address_to_unique_tokens(contract_address_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
contract_address_hash
|> TokenTransfer.address_to_unique_tokens()
|> TokenTransfer.page_token_transfer(paging_options)
|> limit(^paging_options.page_size)
|> Repo.all()
end
end

@ -26,7 +26,7 @@ defmodule Explorer.Chain.TokenTransfer do
import Ecto.{Changeset, Query}
alias Explorer.Chain.{Address, Block, Hash, Transaction, TokenTransfer}
alias Explorer.Chain.{Address, Block, Hash, Transaction, Token, TokenTransfer}
alias Explorer.{PagingOptions, Repo}
@default_paging_options %PagingOptions{page_size: 50}
@ -141,6 +141,14 @@ defmodule Explorer.Chain.TokenTransfer do
def page_token_transfer(query, %PagingOptions{key: nil}), do: query
def page_token_transfer(query, %PagingOptions{key: {token_id}}) do
where(
query,
[token_transfer],
token_transfer.token_id > ^token_id
)
end
def page_token_transfer(query, %PagingOptions{key: inserted_at}) do
where(
query,
@ -166,4 +174,27 @@ defmodule Explorer.Chain.TokenTransfer do
|> join(:left, [transaction], tt in assoc(transaction, :token_transfers))
|> where([_transaction, tt], tt.to_address_hash == ^address_hash or tt.from_address_hash == ^address_hash)
end
@doc """
A token ERC-721 is considered unique because it corresponds to the possession
of a specific asset.
To find out its current owner, it is necessary to look at the token last
transfer.
"""
@spec address_to_unique_tokens(Hash.Address.t()) :: %Ecto.Query{}
def address_to_unique_tokens(contract_address_hash) do
from(
tt in TokenTransfer,
join: t in Token,
on: tt.token_contract_address_hash == t.contract_address_hash,
join: ts in Transaction,
on: tt.transaction_hash == ts.hash,
where: t.contract_address_hash == ^contract_address_hash and t.type == "ERC-721",
order_by: [desc: ts.block_number],
distinct: tt.token_id,
preload: [:to_address],
select: tt
)
end
end

@ -3,7 +3,7 @@ defmodule Explorer.Chain.TokenTransferTest do
import Explorer.Factory
alias Explorer.PagingOptions
alias Explorer.{PagingOptions, Repo}
alias Explorer.Chain.TokenTransfer
doctest Explorer.Chain.TokenTransfer
@ -142,4 +142,73 @@ defmodule Explorer.Chain.TokenTransferTest do
assert TokenTransfer.count_token_transfers_from_token_hash(token_contract_address.hash) == 2
end
end
describe "address_to_unique_tokens/2" do
test "returns list of unique tokens for a token contract" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 42
)
another_transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 2))
last_owner =
insert(
:token_transfer,
to_address: build(:address),
transaction: another_transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 42
)
results =
token_contract_address.hash
|> TokenTransfer.address_to_unique_tokens()
|> Repo.all()
assert Enum.map(results, & &1.token_id) == [last_owner.token_id]
assert Enum.map(results, & &1.to_address_hash) == [last_owner.to_address_hash]
end
test "won't return tokens that aren't uniques" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-20")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
results =
token_contract_address.hash
|> TokenTransfer.address_to_unique_tokens()
|> Repo.all()
assert results == []
end
end
end

@ -2819,4 +2819,47 @@ defmodule Explorer.ChainTest do
assert result == [transaction.hash]
end
end
describe "address_to_unique_tokens/2" do
test "unique tokens can be paginated through token_id" do
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address, type: "ERC-721")
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1))
first_page =
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 11
)
second_page =
insert(
:token_transfer,
to_address: build(:address),
transaction: transaction,
token_contract_address: token_contract_address,
token: token,
token_id: 29
)
paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1}
unique_tokens_ids_paginated =
Chain.address_to_unique_tokens(
token_contract_address.hash,
paging_options: paging_options
)
|> Enum.map(& &1.token_id)
assert unique_tokens_ids_paginated == [second_page.token_id]
end
end
end

Loading…
Cancel
Save