ERC-1155 transfers support in UI

pull/3902/head
Viktor Baranov 4 years ago
parent 9386adfb0b
commit d8b6f073c1
  1. 1
      .dialyzer-ignore
  2. 9
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
  3. 6
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
  4. 12
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex
  5. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/_token_balances.html.eex
  6. 9
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  7. 9
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_total_transfers.html.eex
  8. 21
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  9. 9
      apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex
  10. 6
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  11. 8
      apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
  12. 12
      apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
  13. 1
      apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
  14. 1
      apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
  15. 2
      apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex
  16. 163
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  17. 6
      apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs
  18. 8
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  19. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  20. 56
      apps/explorer/lib/explorer/chain.ex
  21. 8
      apps/explorer/lib/explorer/chain/address/current_token_balance.ex
  22. 1
      apps/explorer/lib/explorer/chain/token.ex
  23. 1
      apps/explorer/lib/explorer/chain/token_transfer.ex
  24. 41
      apps/explorer/lib/explorer/token/balance_reader.ex
  25. 12
      apps/explorer/priv/repo/migrations/20210422115740_add_token_id_to_current_token_balances.exs
  26. 9
      apps/explorer/test/explorer/chain/import_test.exs
  27. 16
      apps/indexer/test/indexer/fetcher/token_balance_test.exs

@ -28,3 +28,4 @@ lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/7 has no
lib/explorer/staking/stake_snapshotting.ex:147
lib/explorer/third_party_integrations/sourcify.ex:65
lib/explorer/third_party_integrations/sourcify.ex:68
lib/block_scout_web/views/transaction_view.ex:141

@ -45,6 +45,15 @@
) %>
<% end %>
<%= if Enum.any?(@token_balances, & &1.token.type == "ERC-1155") do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-1155"),
type: "ERC-1155"
) %>
<% end %>
<%= if Enum.any?(@token_balances, & &1.token.type == "ERC-20") do %>
<%= render(
"_tokens.html",

@ -23,8 +23,12 @@
<% end %>
</div>
<div class="row">
<p class="mb-0 col-md-6">
<% col_md = if token_balance.token.usd_value, do: "col-md-6", else: "col-md-12" %>
<p class="mb-0 <%= col_md%> ">
<%= format_according_to_decimals(token_balance.value, token_balance.token.decimals) %> <%= token_balance.token.symbol %>
<%= if token_balance.token_type == "ERC-1155" do %>
<%= " TokenID " <> to_string(token_balance.token_id) %>
<% end %>
</p>
<%= if token_balance.token.usd_value do %>
<p class="mb-0 col-md-6 text-right text-muted">

@ -0,0 +1,12 @@
<%= case @type do %>
<% :token_burning -> %>
<%= gettext("Token Burning") %>
<% :token_minting -> %>
<%= gettext("Token Minting") %>
<% :token_spawning -> %>
<%= gettext("Token Creation") %>
<% :token_transfer -> %>
<%= gettext("Token Transfer") %>
<% _ -> %>
<%= gettext("Token Transfer") %>
<% end %>

@ -7,7 +7,7 @@
<span>
<span class="text-dark">
<%= format_token_balance_value(@token_balance.value, @token) %> <%= @token.symbol %>
<%= format_token_balance_value(@token_balance.value, @token_balance.token_id, @token) %> <%= @token.symbol %>
</span>
<%= if show_total_supply_percentage?(@token.total_supply) do %>

@ -3,14 +3,7 @@
<!-- Color Block -->
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<%= cond do %>
<% @token_transfer.to_address.hash == @burn_address_hash -> %>
<%= gettext("Token Burning") %>
<% @token_transfer.from_address.hash == @burn_address_hash -> %>
<%= gettext("Token Minting") %>
<% true -> %>
<%= gettext("Token Transfer") %>
<% end %>
<%= render(BlockScoutWeb.CommonComponentsView, "_token_transfer_type_display_name.html", type: Chain.get_token_transfer_type(@token_transfer)) %>
</span>
</div>
<!-- Content -->

@ -1,8 +1,15 @@
<%= case token_transfer_amount(@transfer) do %>
<% {:ok, :erc721_instance} -> %>
<%= "TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<% {:ok, :erc1155_instance, value} -> %>
<% transfer_type = Chain.get_token_transfer_type(@transfer) %>
<%= if transfer_type == :token_spawning do %>
<%= "TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<% else %>
<%= "#{value} TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<% end %>
<% {:ok, value} -> %>
<%= value %>
<% end %>
<%= " "%>
<%= " " %>
<%= link(token_symbol(@transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash), "data-test": "token_link") %>

@ -225,18 +225,17 @@
<% end %>
</div>
<% end %>
<% token_transfers = aggregate_token_transfers(transaction_with_transfers.token_transfers) %>
<%= if Enum.count(token_transfers) > 0 do %>
<% %{transfers: transfers, mintings: mintings, burnings: burnings, creations: creations} = aggregate_token_transfers(transaction_with_transfers.token_transfers) %>
<%= if Enum.count(transfers) > 0 do %>
<h2 class="card-title balance-card-title"><%= token_type_name(type)%><%= gettext " Token Transfer" %></h2>
<div class="text-right">
<%= for transfer <- token_transfers do %>
<%= for transfer <- transfers do %>
<h3 class="address-balance-text">
<%= render BlockScoutWeb.TransactionView, "_total_transfers.html", Map.put(assigns, :transfer, transfer) %>
</h3>
<% end %>
</div>
<% end %>
<% mintings = aggregate_token_mintings(transaction_with_transfers.token_transfers) %>
<%= if Enum.count(mintings) > 0 do %>
<h2 class="card-title balance-card-title"><%= token_type_name(type)%><%= gettext " Token Minting" %></h2>
<div class="text-right">
@ -247,7 +246,6 @@
<% end %>
</div>
<% end %>
<% burnings = aggregate_token_burnings(transaction_with_transfers.token_transfers) %>
<%= if Enum.count(burnings) > 0 do %>
<h2 class="card-title balance-card-title"><%= token_type_name(type)%><%= gettext " Token Burning" %></h2>
<div class="text-right">
@ -258,6 +256,19 @@
<% end %>
</div>
<% end %>
<%# ERC-1155 token creations %>
<%= if Enum.count(creations) > 0 do %>
<h2 class="card-title balance-card-title"><%= token_type_name(type)%><%= gettext " Token Creation" %></h2>
<div class="text-right">
<%= for transfer <- creations do %>
<h3 class="address-balance-text">
<%= "TokenID ["%><%= link(short_token_id(transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, transfer.token.contract_address_hash, to_string(transfer.token_id)), "data-test": "token_link") %><%= "]" %>
<%= " "%>
<%= link(token_symbol(transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, transfer.token.contract_address_hash), "data-test": "token_link") %>
</h3>
<% end %>
</div>
<% end %>
</div>
</div>
<% _ -> %>

@ -1,14 +1,7 @@
<div class="tile tile-type-token-transfer fade-in">
<div class="row justify-content-end">
<div class="col-12 col-md-4 col-lg-2 d-flex align-items-center justify-content-start justify-content-lg-center tile-label">
<%= cond do %>
<% @token_transfer.to_address.hash == @burn_address_hash -> %>
<%= gettext("Token Burning") %>
<% @token_transfer.from_address.hash == @burn_address_hash -> %>
<%= gettext("Token Minting") %>
<% true -> %>
<%= gettext("Token Transfer") %>
<% end %>
<%= render(BlockScoutWeb.CommonComponentsView, "_token_transfer_type_display_name.html", type: Chain.get_token_transfer_type(@token_transfer)) %>
</div>
<div class="col-12 col-md-8 col-lg-10 d-flex flex-column text-nowrap">

@ -172,6 +172,12 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
|> Map.put_new(:tokenID, token_transfer.token_id)
end
defp prepare_token_transfer(%{token_type: "ERC-1155"} = token_transfer) do
token_transfer
|> prepare_common_token_transfer()
|> Map.put_new(:tokenID, token_transfer.token_id)
end
defp prepare_token_transfer(%{token_type: "ERC-20"} = token_transfer) do
token_transfer
|> prepare_common_token_transfer()

@ -32,14 +32,14 @@ defmodule BlockScoutWeb.Tokens.Helpers do
{:ok, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, _token_id) do
{:ok, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _token_id) do
{:ok, :erc721_instance}
end
defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, _token_id) do
{:ok, :erc1155_instance, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
defp do_token_transfer_amount(_token, _amount, _token_id) do
nil
end

@ -50,19 +50,23 @@ defmodule BlockScoutWeb.Tokens.HolderView do
## Examples
iex> token = build(:token, type: "ERC-20", decimals: Decimal.new(2))
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, token)
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(100000, nil, token)
"1,000"
iex> token = build(:token, type: "ERC-721")
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, token)
iex> BlockScoutWeb.Tokens.HolderView.format_token_balance_value(1, nil, token)
1
"""
def format_token_balance_value(value, %Token{type: "ERC-20", decimals: decimals}) do
def format_token_balance_value(value, _id, %Token{type: "ERC-20", decimals: decimals}) do
format_according_to_decimals(value, decimals)
end
def format_token_balance_value(value, _token) do
def format_token_balance_value(value, id, %Token{type: "ERC-1155", decimals: decimals}) do
to_string(format_according_to_decimals(value, decimals)) <> " TokenID " <> to_string(id)
end
def format_token_balance_value(value, _id, _token) do
value
end
end

@ -44,6 +44,7 @@ defmodule BlockScoutWeb.Tokens.OverviewView do
defp tab_name(["inventory"]), do: gettext("Inventory")
def display_inventory?(%Token{type: "ERC-721"}), do: true
def display_inventory?(%Token{type: "ERC-1155"}), do: true
def display_inventory?(_), do: false
def smart_contract_with_read_only_functions?(

@ -2,5 +2,6 @@ defmodule BlockScoutWeb.Tokens.TransferView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
alias Explorer.Chain
alias Explorer.Chain.Address
end

@ -1,3 +1,5 @@
defmodule BlockScoutWeb.TransactionTokenTransferView do
use BlockScoutWeb, :view
alias Explorer.Chain
end

@ -16,16 +16,15 @@ defmodule BlockScoutWeb.TransactionView do
@tabs ["token-transfers", "internal-transactions", "logs", "raw-trace"]
{:ok, burn_address_hash} = Chain.string_to_address_hash("0x0000000000000000000000000000000000000000")
@burn_address_hash burn_address_hash
@token_burning_title "Token Burning"
@token_minting_title "Token Minting"
@token_transfer_title "Token Transfer"
@token_creation_title "Token Creation"
@token_burning_type "token-burning"
@token_minting_type "token-minting"
@token_transfer_type "token-transfer"
@token_burning_type :token_burning
@token_minting_type :token_minting
@token_creation_type :token_spawning
@token_transfer_type :token_transfer
defguardp is_transaction_type(mod) when mod in [InternalTransaction, Transaction]
@ -63,52 +62,80 @@ defmodule BlockScoutWeb.TransactionView do
end
def aggregate_token_transfers(token_transfers) do
{transfers, nft_transfers} =
%{
transfers: {ft_transfers, nft_transfers},
mintings: {ft_mintings, nft_mintings},
burnings: {ft_burnings, nft_burnings},
creations: {ft_creations, nft_creations}
} =
token_transfers
|> Enum.reduce({%{}, []}, fn token_transfer, acc ->
if token_transfer.to_address_hash != @burn_address_hash &&
token_transfer.from_address_hash != @burn_address_hash do
aggregate_reducer(token_transfer, acc)
else
acc
|> Enum.reduce(
%{
transfers: {%{}, []},
mintings: {%{}, []},
burnings: {%{}, []},
creations: {%{}, []}
},
fn token_transfer, acc ->
token_transfer_type = Chain.get_token_transfer_type(token_transfer)
case token_transfer_type do
:token_transfer ->
transfers = aggregate_reducer(token_transfer, acc.transfers)
%{
transfers: transfers,
mintings: acc.mintings,
burnings: acc.burnings,
creations: acc.creations
}
:token_burning ->
burnings = aggregate_reducer(token_transfer, acc.burnings)
%{
transfers: acc.transfers,
mintings: acc.mintings,
burnings: burnings,
creations: acc.creations
}
:token_minting ->
mintings = aggregate_reducer(token_transfer, acc.mintings)
%{
transfers: acc.transfers,
mintings: mintings,
burnings: acc.burnings,
creations: acc.creations
}
:token_spawning ->
creations = aggregate_reducer(token_transfer, acc.creations)
%{
transfers: acc.transfers,
mintings: acc.mintings,
burnings: acc.burnings,
creations: creations
}
end
end
end)
)
final_transfers = Map.values(transfers)
final_ft_transfers = Map.values(ft_transfers)
transfers = final_ft_transfers ++ nft_transfers
final_transfers ++ nft_transfers
end
final_ft_mintings = Map.values(ft_mintings)
mintings = final_ft_mintings ++ nft_mintings
def aggregate_token_mintings(token_transfers) do
{transfers, nft_transfers} =
token_transfers
|> Enum.reduce({%{}, []}, fn token_transfer, acc ->
if token_transfer.from_address_hash == @burn_address_hash do
aggregate_reducer(token_transfer, acc)
else
acc
end
end)
final_ft_burnings = Map.values(ft_burnings)
burnings = final_ft_burnings ++ nft_burnings
final_transfers = Map.values(transfers)
final_ft_creations = Map.values(ft_creations)
creations = final_ft_creations ++ nft_creations
final_transfers ++ nft_transfers
end
def aggregate_token_burnings(token_transfers) do
{transfers, nft_transfers} =
token_transfers
|> Enum.reduce({%{}, []}, fn token_transfer, acc ->
if token_transfer.to_address_hash == @burn_address_hash do
aggregate_reducer(token_transfer, acc)
else
acc
end
end)
final_transfers = Map.values(transfers)
final_transfers ++ nft_transfers
%{transfers: transfers, mintings: mintings, burnings: burnings, creations: creations}
end
defp aggregate_reducer(%{amount: amount} = token_transfer, {acc1, acc2}) when is_nil(amount) do
@ -147,6 +174,7 @@ defmodule BlockScoutWeb.TransactionView do
case type do
:erc20 -> gettext("ERC-20 ")
:erc721 -> gettext("ERC-721 ")
:erc1155 -> gettext("ERC-1155 ")
_ -> ""
end
end
@ -336,11 +364,12 @@ defmodule BlockScoutWeb.TransactionView do
def transaction_display_type(%Transaction{} = transaction) do
cond do
involves_token_transfers?(transaction) ->
token_transfer_type = get_token_transfer_type(transaction.token_transfers)
token_transfer_type = get_transaction_type_from_token_transfers(transaction.token_transfers)
case token_transfer_type do
@token_minting_type -> gettext(@token_minting_title)
@token_burning_type -> gettext(@token_burning_title)
@token_creation_type -> gettext(@token_creation_title)
@token_transfer_type -> gettext(@token_transfer_title)
end
@ -407,35 +436,27 @@ defmodule BlockScoutWeb.TransactionView do
defp tab_name(["logs"]), do: gettext("Logs")
defp tab_name(["raw-trace"]), do: gettext("Raw Trace")
defp get_token_transfer_type(token_transfers) do
token_transfers
|> Enum.reduce("", fn token_transfer, type ->
cond do
token_transfer.to_address_hash == @burn_address_hash ->
update_transfer_type_if_burning(type)
defp get_transaction_type_from_token_transfers(token_transfers) do
token_transfers_types =
token_transfers
|> Enum.map(fn token_transfer ->
Chain.get_token_transfer_type(token_transfer)
end)
token_transfer.from_address_hash == @burn_address_hash ->
update_transfer_type_if_minting(type)
burnings_count =
Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_burning_type end)
true ->
@token_transfer_type
end
end)
end
mintings_count =
Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_minting_type end)
defp update_transfer_type_if_minting(type) do
case type do
"" -> @token_minting_type
@token_burning_type -> @token_transfer_type
_ -> type
end
end
creations_count =
Enum.count(token_transfers_types, fn token_transfers_type -> token_transfers_type == @token_creation_type end)
defp update_transfer_type_if_burning(type) do
case type do
"" -> @token_burning_type
@token_minting_type -> @token_transfer_type
_ -> type
cond do
Enum.count(token_transfers_types) == burnings_count -> @token_burning_type
Enum.count(token_transfers_types) == mintings_count -> @token_minting_type
Enum.count(token_transfers_types) == creations_count -> @token_creation_type
true -> @token_transfer_type
end
end
end

@ -42,19 +42,19 @@ defmodule BlockScoutWeb.Tokens.HolderViewTest do
end
end
describe "format_token_balance_value/1" do
describe "format_token_balance_value/3" do
test "formats according to token decimals when it's a ERC-20" do
token = build(:token, type: "ERC-20", decimals: Decimal.new(2))
token_balance = build(:token_balance, value: 2_000_000)
assert HolderView.format_token_balance_value(token_balance.value, token) == "20,000"
assert HolderView.format_token_balance_value(token_balance.value, nil, token) == "20,000"
end
test "returns the value when it's ERC-721" do
token = build(:token, type: "ERC-721")
token_balance = build(:token_balance, value: 1)
assert HolderView.format_token_balance_value(token_balance.value, token) == 1
assert HolderView.format_token_balance_value(token_balance.value, nil, token) == 1
end
end
end

@ -264,8 +264,8 @@ defmodule BlockScoutWeb.TransactionViewTest do
result = TransactionView.aggregate_token_transfers([token_transfer, token_transfer, token_transfer])
assert Enum.count(result) == 1
assert List.first(result).amount == Decimal.new(3)
assert Enum.count(result.transfers) == 1
assert List.first(result.transfers).amount == Decimal.new(3)
end
test "does not aggregate NFT tokens" do
@ -278,8 +278,8 @@ defmodule BlockScoutWeb.TransactionViewTest do
result = TransactionView.aggregate_token_transfers([token_transfer, token_transfer, token_transfer])
assert Enum.count(result) == 3
assert List.first(result).amount == nil
assert Enum.count(result.transfers) == 3
assert List.first(result.transfers).amount == nil
end
end
end

@ -168,7 +168,11 @@ defmodule EthereumJSONRPC do
"""
@spec execute_contract_functions([Contract.call()], [map()], json_rpc_named_arguments) :: [Contract.call_result()]
def execute_contract_functions(functions, abi, json_rpc_named_arguments) do
Contract.execute_contract_functions(functions, abi, json_rpc_named_arguments)
if Enum.count(functions) > 0 do
Contract.execute_contract_functions(functions, abi, json_rpc_named_arguments)
else
[]
end
end
@doc """

@ -98,6 +98,8 @@ defmodule Explorer.Chain do
# keccak256("Error(string)")
@revert_error_method_id "08c379a0"
@burn_address_hash_str "0x0000000000000000000000000000000000000000"
@typedoc """
The name of an association on the `t:Ecto.Schema.t/0`
"""
@ -1635,7 +1637,7 @@ defmodule Explorer.Chain do
@spec fetch_sum_coin_total_supply_minus_burnt() :: non_neg_integer
def fetch_sum_coin_total_supply_minus_burnt do
{:ok, burn_address_hash} = string_to_address_hash("0x0000000000000000000000000000000000000000")
{:ok, burn_address_hash} = Chain.string_to_address_hash(@burn_address_hash_str)
query =
from(
@ -5187,7 +5189,7 @@ defmodule Explorer.Chain do
end
@spec transaction_token_transfer_type(Transaction.t()) ::
:erc20 | :erc721 | :token_transfer | nil
:erc20 | :erc721 | :erc1155 | :token_transfer | nil
def transaction_token_transfer_type(
%Transaction{
status: :ok,
@ -5234,10 +5236,24 @@ defmodule Explorer.Chain do
find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address})
# safeTransferFrom(address,address,uint256,uint256,bytes)
{"0xf242432a" <> params, ^zero_wei} ->
types = [:address, :address, {:uint, 256}, {:uint, 256}, :bytes]
[from_address, to_address, _id, _value, _data] = decode_params(params, types)
find_erc1155_token_transfer(transaction.token_transfers, {from_address, to_address})
# safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)
{"0x2eb2c2d6" <> params, ^zero_wei} ->
types = [:address, :address, [{:uint, 256}], [{:uint, 256}], :bytes]
[from_address, to_address, _ids, _values, _data] = decode_params(params, types)
find_erc1155_token_transfer(transaction.token_transfers, {from_address, to_address})
{"0xf907fc5b" <> _params, ^zero_wei} ->
:erc20
# check for ERC 20 or for old ERC 721 token versions
# check for ERC-20 or for old ERC-721, ERC-1155 token versions
{unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} ->
types = [:address, {:uint, 256}]
@ -5245,7 +5261,7 @@ defmodule Explorer.Chain do
decimal_value = Decimal.new(value)
find_erc721_or_erc20_token_transfer(transaction.token_transfers, {address, decimal_value})
find_erc721_or_erc20_or_erc1155_token_transfer(transaction.token_transfers, {address, decimal_value})
_ ->
nil
@ -5261,7 +5277,16 @@ defmodule Explorer.Chain do
if token_transfer, do: :erc721
end
defp find_erc721_or_erc20_token_transfer(token_transfers, {address, decimal_value}) do
defp find_erc1155_token_transfer(token_transfers, {from_address, to_address}) do
token_transfer =
Enum.find(token_transfers, fn token_transfer ->
token_transfer.from_address_hash.bytes == from_address && token_transfer.to_address_hash.bytes == to_address
end)
if token_transfer, do: :erc1155
end
defp find_erc721_or_erc20_or_erc1155_token_transfer(token_transfers, {address, decimal_value}) do
token_transfer =
Enum.find(token_transfers, fn token_transfer ->
token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value
@ -5271,6 +5296,7 @@ defmodule Explorer.Chain do
case token_transfer.token do
%Token{type: "ERC-20"} -> :erc20
%Token{type: "ERC-721"} -> :erc721
%Token{type: "ERC-1155"} -> :erc1155
_ -> nil
end
else
@ -6317,4 +6343,24 @@ defmodule Explorer.Chain do
_ -> ""
end
end
@spec get_token_transfer_type(TokenTransfer.t()) ::
:token_burning | :token_minting | :token_spawning | :token_transfer
def get_token_transfer_type(transfer) do
{:ok, burn_address_hash} = Chain.string_to_address_hash(@burn_address_hash_str)
cond do
transfer.to_address_hash == burn_address_hash && transfer.from_address_hash !== burn_address_hash ->
:token_burning
transfer.to_address_hash !== burn_address_hash && transfer.from_address_hash == burn_address_hash ->
:token_minting
transfer.to_address_hash == burn_address_hash && transfer.from_address_hash == burn_address_hash ->
:token_spawning
true ->
:token_transfer
end
end
end

@ -23,6 +23,8 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
* `token_contract_address_hash` - The contract address hash foreign key.
* `block_number` - The block's number that the transfer took place.
* `value` - The value that's represents the balance.
* `token_id` - The token_id of the transferred token (applicable for ERC-1155 and ERC-721 tokens)
* `token_type` - The type of the token
"""
@type t :: %__MODULE__{
address: %Ecto.Association.NotLoaded{} | Address.t(),
@ -39,6 +41,8 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
field(:value, :decimal)
field(:block_number, :integer)
field(:value_fetched_at, :utc_datetime_usec)
field(:token_id, :decimal)
field(:token_type, :string)
# A transient field for deriving token holder count deltas during address_current_token_balances upserts
field(:old_value, :decimal)
@ -56,8 +60,8 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
timestamps()
end
@optional_fields ~w(value value_fetched_at)a
@required_fields ~w(address_hash block_number token_contract_address_hash)a
@optional_fields ~w(value value_fetched_at token_id)a
@required_fields ~w(address_hash block_number token_contract_address_hash token_type)a
@allowed_fields @optional_fields ++ @required_fields
@doc false

@ -8,6 +8,7 @@ defmodule Explorer.Chain.Token do
* ERC-20
* ERC-721
* ERC-1155
## Token Specifications

@ -300,6 +300,7 @@ defmodule Explorer.Chain.TokenTransfer do
left_join: instance in Instance,
on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id,
where: tt.token_contract_address_hash == ^contract_address_hash,
where: tt.to_address_hash != ^"0x0000000000000000000000000000000000000000",
order_by: [desc: tt.block_number],
distinct: [desc: tt.token_id],
preload: [:to_address],

@ -45,6 +45,13 @@ defmodule Explorer.Token.BalanceReader do
def get_balances_of(token_balance_requests) do
regular_balances =
token_balance_requests
|> Enum.filter(fn request ->
if Map.has_key?(request, :token_type) do
request.token_type !== "ERC-1155"
else
true
end
end)
|> Enum.map(&format_balance_request/1)
|> Reader.query_contracts(@balance_function_abi)
|> Enum.map(&format_balance_result/1)
@ -52,21 +59,13 @@ defmodule Explorer.Token.BalanceReader do
erc1155_balances =
token_balance_requests
|> Enum.filter(fn request ->
request.token_type == "ERC-1155"
end)
|> Enum.map(fn %{
address_hash: address_hash,
block_number: block_number,
token_contract_address_hash: token_contract_address_hash,
token_id: token_id
} ->
%{
contract_address: token_contract_address_hash,
function_name: "balanceOf",
args: [address_hash, token_id],
block_number: block_number
}
if Map.has_key?(request, :token_type) do
request.token_type == "ERC-1155"
else
false
end
end)
|> Enum.map(&format_erc_1155_balance_request/1)
|> Reader.query_contracts(@erc1155_balance_function_abi)
|> Enum.map(&format_balance_result/1)
@ -86,6 +85,20 @@ defmodule Explorer.Token.BalanceReader do
}
end
defp format_erc_1155_balance_request(%{
address_hash: address_hash,
block_number: block_number,
token_contract_address_hash: token_contract_address_hash,
token_id: token_id
}) do
%{
contract_address: token_contract_address_hash,
method_id: "00fdd58e",
args: [address_hash, token_id],
block_number: block_number
}
end
defp format_balance_result({:ok, [balance]}) do
{:ok, balance}
end

@ -0,0 +1,12 @@
defmodule Explorer.Repo.Migrations.AddTokenIdToCurrentTokenBalances do
use Ecto.Migration
def change do
alter table(:address_current_token_balances) do
add(:token_id, :numeric, precision: 78, scale: 0, null: true)
add(:token_type, :string, null: true)
end
create(index(:address_current_token_balances, [:token_id]))
end
end

@ -428,13 +428,15 @@ defmodule Explorer.Chain.ImportTest do
address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: "37",
value: 200
value: 200,
token_type: "ERC-20"
},
%{
address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_number: "37",
value: 100
value: 100,
token_type: "ERC-20"
}
],
timeout: 5
@ -2264,7 +2266,8 @@ defmodule Explorer.Chain.ImportTest do
address_hash: address_hash,
token_contract_address_hash: token_contract_address_hash,
block_number: block_number,
value: value_after
value: value_after,
token_type: "ERC-20"
}
]
},

@ -76,22 +76,6 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
token_balance_a = insert(:token_balance, value_fetched_at: nil, value: nil)
token_balance_b = insert(:token_balance, value_fetched_at: nil, value: nil)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: id, method: "eth_call", params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."},
id: id,
jsonrpc: "2.0"
}
]}
end
)
token_balances = [
{
token_balance_a.address_hash.bytes,

Loading…
Cancel
Save