- <%= 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)) %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
index 30f9803259..cdc8b8375a 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
@@ -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()
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
index 7652fa14ad..7b8d3276d6 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helpers.ex
@@ -16,27 +16,39 @@ defmodule BlockScoutWeb.Tokens.Helpers do
When the token's type is ERC-721, the function will return a string with the token_id that
represents the ERC-721 token since this kind of token doesn't have amount and decimals.
"""
+ def token_transfer_amount(%{token: token, amount: amount, amounts: amounts, token_id: token_id, token_ids: token_ids}) do
+ do_token_transfer_amount(token, amount, amounts, token_id, token_ids)
+ end
+
def token_transfer_amount(%{token: token, amount: amount, token_id: token_id}) do
- do_token_transfer_amount(token, amount, token_id)
+ do_token_transfer_amount(token, amount, nil, token_id, nil)
end
- defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, _token_id) do
+ defp do_token_transfer_amount(%Token{type: "ERC-20"}, nil, nil, _token_id, _token_ids) do
{:ok, "--"}
end
- defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, amount, _token_id) do
+ defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: nil}, amount, _amounts, _token_id, _token_ids) do
{:ok, CurrencyHelpers.format_according_to_decimals(amount, Decimal.new(0))}
end
- defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, amount, _token_id) do
+ defp do_token_transfer_amount(%Token{type: "ERC-20", decimals: decimals}, amount, _amounts, _token_id, _token_ids) do
{:ok, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
end
- defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _token_id) do
+ defp do_token_transfer_amount(%Token{type: "ERC-721"}, _amount, _amounts, _token_id, _token_ids) do
{:ok, :erc721_instance}
end
- defp do_token_transfer_amount(_token, _amount, _token_id) do
+ defp do_token_transfer_amount(%Token{type: "ERC-1155", decimals: decimals}, amount, amounts, _token_id, token_ids) do
+ if amount do
+ {:ok, :erc1155_instance, CurrencyHelpers.format_according_to_decimals(amount, decimals)}
+ else
+ {:ok, :erc1155_instance, amounts, token_ids, decimals}
+ end
+ end
+
+ defp do_token_transfer_amount(_token, _amount, _amounts, _token_id, _token_ids) do
nil
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
index e185e3f9e8..2edfd93981 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/holder_view.ex
@@ -54,19 +54,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
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/holder_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/holder_view.ex
new file mode 100644
index 0000000000..38cf207bc4
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/holder_view.ex
@@ -0,0 +1,5 @@
+defmodule BlockScoutWeb.Tokens.Instance.HolderView do
+ use BlockScoutWeb, :view
+
+ alias BlockScoutWeb.Tokens.Instance.OverviewView
+end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex
index a539ef3d7f..2e396b2b50 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/instance/overview_view.ex
@@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Tokens.Instance.OverviewView do
use BlockScoutWeb, :view
alias BlockScoutWeb.CurrencyHelpers
+ alias Explorer.Chain
alias Explorer.Chain.{Address, SmartContract, Token}
alias Explorer.SmartContract.Helper
alias FileInfo
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex
index e720e1b105..547d6dd33a 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/inventory_view.ex
@@ -4,4 +4,5 @@ defmodule BlockScoutWeb.Tokens.InventoryView do
import BlockScoutWeb.Tokens.Instance.OverviewView, only: [media_src: 1, media_type: 1]
alias BlockScoutWeb.Tokens.OverviewView
+ alias Explorer.Chain
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
index f18b68ee74..b2ac3a6832 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/overview_view.ex
@@ -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?(
diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
index 96f29c5fe0..3ea5d84840 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/transfer_view.ex
@@ -2,5 +2,6 @@ defmodule BlockScoutWeb.Tokens.TransferView do
use BlockScoutWeb, :view
alias BlockScoutWeb.Tokens.OverviewView
+ alias Explorer.Chain
alias Explorer.Chain.Address
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex
index eb8fe81053..66999ad804 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_token_transfer_view.ex
@@ -1,3 +1,5 @@
defmodule BlockScoutWeb.TransactionTokenTransferView do
use BlockScoutWeb, :view
+
+ alias Explorer.Chain
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
index 84856e8af9..afe6fe67dc 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
@@ -184,33 +184,24 @@ defmodule BlockScoutWeb.TransactionView do
end)
new_acc1 =
- if token_transfer.token_id do
- [new_entry | acc1]
- else
- if existing_entry do
- acc1
- |> Enum.map(fn entry ->
- if entry.to_address_hash == token_transfer.to_address_hash &&
- entry.from_address_hash == token_transfer.from_address_hash &&
- entry.token == token_transfer.token do
- updated_entry =
- if new_entry.amount do
- %{
- entry
- | amount: Decimal.add(new_entry.amount, entry.amount)
- }
- else
- entry
- end
-
- updated_entry
- else
+ if existing_entry do
+ acc1
+ |> Enum.map(fn entry ->
+ if entry.to_address_hash == token_transfer.to_address_hash &&
+ entry.from_address_hash == token_transfer.from_address_hash &&
+ entry.token == token_transfer.token do
+ updated_entry = %{
entry
- end
- end)
- else
- [new_entry | acc1]
- end
+ | amount: Decimal.add(new_entry.amount, entry.amount)
+ }
+
+ updated_entry
+ else
+ entry
+ end
+ end)
+ else
+ [new_entry | acc1]
end
{new_acc1, acc2}
@@ -220,6 +211,7 @@ defmodule BlockScoutWeb.TransactionView do
case type do
:erc20 -> gettext("ERC-20 ")
:erc721 -> gettext("ERC-721 ")
+ :erc1155 -> gettext("ERC-1155 ")
_ -> ""
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex
index b0b7ad9998..602dea1bfe 100644
--- a/apps/block_scout_web/lib/block_scout_web/web_router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex
@@ -267,6 +267,13 @@ defmodule BlockScoutWeb.WebRouter do
only: [:index],
as: :metadata
)
+
+ resources(
+ "/token-holders",
+ Tokens.Instance.HolderController,
+ only: [:index],
+ as: :holder
+ )
end
end
@@ -318,6 +325,13 @@ defmodule BlockScoutWeb.WebRouter do
only: [:index],
as: :metadata
)
+
+ resources(
+ "/token-holders",
+ Tokens.Instance.HolderController,
+ only: [:index],
+ as: :holder
+ )
end
end
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index ea952ce7c8..ac18805dc5 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -81,7 +81,7 @@ msgid "%{subnetwork} Staking DApp - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:356
+#: lib/block_scout_web/views/transaction_view.ex:348
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -348,7 +348,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_link.html.eex:2
-#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:28 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:50
+#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:28 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43
msgid "Block #%{number}"
msgstr ""
@@ -580,7 +580,7 @@ msgid "Compiler version"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:349
+#: lib/block_scout_web/views/transaction_view.ex:341
msgid "Confirmed"
msgstr ""
@@ -654,12 +654,12 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:460
+#: lib/block_scout_web/views/transaction_view.ex:452
msgid "Contract Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:457
+#: lib/block_scout_web/views/transaction_view.ex:449
msgid "Contract Creation"
msgstr ""
@@ -987,12 +987,12 @@ msgid "EIP-1167"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:221
+#: lib/block_scout_web/views/transaction_view.ex:212
msgid "ERC-20 "
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:222
+#: lib/block_scout_web/views/transaction_view.ex:213
msgid "ERC-721 "
msgstr ""
@@ -1061,12 +1061,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:360
+#: lib/block_scout_web/views/transaction_view.ex:352
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:358
+#: lib/block_scout_web/views/transaction_view.ex:350
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -1313,7 +1313,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:28
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:21 lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 lib/block_scout_web/views/address_view.ex:346
-#: lib/block_scout_web/views/transaction_view.ex:515
+#: lib/block_scout_web/views/transaction_view.ex:507
msgid "Internal Transactions"
msgstr ""
@@ -1440,7 +1440,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:41
#: lib/block_scout_web/templates/address_logs/index.html.eex:10 lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 lib/block_scout_web/views/address_view.ex:357
-#: lib/block_scout_web/views/transaction_view.ex:516
+#: lib/block_scout_web/views/transaction_view.ex:508
msgid "Logs"
msgstr ""
@@ -1481,7 +1481,7 @@ msgid "Max Priority Fee per Gas"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:330
+#: lib/block_scout_web/views/transaction_view.ex:322
msgid "Max of"
msgstr ""
@@ -1497,7 +1497,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18
-#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:178
+#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:179
msgid "Metadata"
msgstr ""
@@ -1684,7 +1684,7 @@ msgid "Other Explorers"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24
msgid "Owner Address"
msgstr ""
@@ -1712,8 +1712,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:53
-#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 lib/block_scout_web/views/transaction_view.ex:355
-#: lib/block_scout_web/views/transaction_view.ex:389
+#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 lib/block_scout_web/views/transaction_view.ex:347
+#: lib/block_scout_web/views/transaction_view.ex:381
msgid "Pending"
msgstr ""
@@ -1831,7 +1831,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
-#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:517
+#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:509
msgid "Raw Trace"
msgstr ""
@@ -2037,9 +2037,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_transaction/index.html.eex:54 lib/block_scout_web/templates/address_validation/index.html.eex:24
#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 lib/block_scout_web/templates/chain/show.html.eex:180
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 lib/block_scout_web/templates/stakes/_table.html.eex:49
-#: lib/block_scout_web/templates/tokens/holder/index.html.eex:27 lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23
-#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:22 lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
-#: lib/block_scout_web/templates/transaction/index.html.eex:29
+#: lib/block_scout_web/templates/tokens/holder/index.html.eex:27 lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23
+#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 lib/block_scout_web/templates/tokens/inventory/index.html.eex:22
+#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 lib/block_scout_web/templates/transaction/index.html.eex:29
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 lib/block_scout_web/templates/transaction_log/index.html.eex:15
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14
msgid "Something went wrong, click to reload."
@@ -2144,7 +2144,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:357
+#: lib/block_scout_web/views/transaction_view.ex:349
msgid "Success"
msgstr ""
@@ -2365,8 +2365,8 @@ msgid "There are no transactions."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28
-#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:26
+#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28
+#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 lib/block_scout_web/templates/tokens/transfer/index.html.eex:26
msgid "There are no transfers for this Token."
msgstr ""
@@ -2477,13 +2477,12 @@ msgid "Token"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
-#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6 lib/block_scout_web/views/transaction_view.ex:451
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 lib/block_scout_web/views/transaction_view.ex:443
msgid "Token Burning"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:452
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 lib/block_scout_web/views/transaction_view.ex:444
msgid "Token Creation"
msgstr ""
@@ -2495,25 +2494,25 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:20
+#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:9 lib/block_scout_web/views/tokens/overview_view.ex:42
msgid "Token Holders"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38
-#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37
msgid "Token ID"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
-#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8 lib/block_scout_web/views/transaction_view.ex:450
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 lib/block_scout_web/views/transaction_view.ex:442
msgid "Token Minting"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
-#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10 lib/block_scout_web/views/transaction_view.ex:453
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 lib/block_scout_web/views/transaction_view.ex:445
msgid "Token Transfer"
msgstr ""
@@ -2523,8 +2522,8 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:3
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 lib/block_scout_web/views/address_view.ex:348
-#: lib/block_scout_web/views/tokens/instance/overview_view.ex:177 lib/block_scout_web/views/tokens/overview_view.ex:41
-#: lib/block_scout_web/views/transaction_view.ex:514
+#: lib/block_scout_web/views/tokens/instance/overview_view.ex:178 lib/block_scout_web/views/tokens/overview_view.ex:41
+#: lib/block_scout_web/views/transaction_view.ex:506
msgid "Token Transfers"
msgstr ""
@@ -2620,7 +2619,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19
-#: lib/block_scout_web/views/transaction_view.ex:463
+#: lib/block_scout_web/views/transaction_view.ex:455
msgid "Transaction"
msgstr ""
@@ -2750,12 +2749,12 @@ msgid "Uncles"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:348
+#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Unconfirmed"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:6
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9
msgid "Unique Token"
msgstr ""
@@ -3191,3 +3190,13 @@ msgstr ""
#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:18
msgid "validator"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/views/transaction_view.ex:214
+msgid "ERC-1155 "
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11
+msgid "Not unique Token"
+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 ea952ce7c8..ac18805dc5 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
@@ -81,7 +81,7 @@ msgid "%{subnetwork} Staking DApp - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:356
+#: lib/block_scout_web/views/transaction_view.ex:348
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -348,7 +348,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/_link.html.eex:2
-#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:28 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:50
+#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:28 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43
msgid "Block #%{number}"
msgstr ""
@@ -580,7 +580,7 @@ msgid "Compiler version"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:349
+#: lib/block_scout_web/views/transaction_view.ex:341
msgid "Confirmed"
msgstr ""
@@ -654,12 +654,12 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:460
+#: lib/block_scout_web/views/transaction_view.ex:452
msgid "Contract Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:457
+#: lib/block_scout_web/views/transaction_view.ex:449
msgid "Contract Creation"
msgstr ""
@@ -987,12 +987,12 @@ msgid "EIP-1167"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:221
+#: lib/block_scout_web/views/transaction_view.ex:212
msgid "ERC-20 "
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:222
+#: lib/block_scout_web/views/transaction_view.ex:213
msgid "ERC-721 "
msgstr ""
@@ -1061,12 +1061,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:360
+#: lib/block_scout_web/views/transaction_view.ex:352
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:358
+#: lib/block_scout_web/views/transaction_view.ex:350
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -1313,7 +1313,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:28
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:21 lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 lib/block_scout_web/views/address_view.ex:346
-#: lib/block_scout_web/views/transaction_view.ex:515
+#: lib/block_scout_web/views/transaction_view.ex:507
msgid "Internal Transactions"
msgstr ""
@@ -1440,7 +1440,7 @@ msgstr ""
#: lib/block_scout_web/templates/address/_tabs.html.eex:41
#: lib/block_scout_web/templates/address_logs/index.html.eex:10 lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 lib/block_scout_web/views/address_view.ex:357
-#: lib/block_scout_web/views/transaction_view.ex:516
+#: lib/block_scout_web/views/transaction_view.ex:508
msgid "Logs"
msgstr ""
@@ -1481,7 +1481,7 @@ msgid "Max Priority Fee per Gas"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:330
+#: lib/block_scout_web/views/transaction_view.ex:322
msgid "Max of"
msgstr ""
@@ -1497,7 +1497,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18
-#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:178
+#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:179
msgid "Metadata"
msgstr ""
@@ -1684,7 +1684,7 @@ msgid "Other Explorers"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24
msgid "Owner Address"
msgstr ""
@@ -1712,8 +1712,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:53
-#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 lib/block_scout_web/views/transaction_view.ex:355
-#: lib/block_scout_web/views/transaction_view.ex:389
+#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 lib/block_scout_web/views/transaction_view.ex:347
+#: lib/block_scout_web/views/transaction_view.ex:381
msgid "Pending"
msgstr ""
@@ -1831,7 +1831,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
-#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:517
+#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:509
msgid "Raw Trace"
msgstr ""
@@ -2037,9 +2037,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_transaction/index.html.eex:54 lib/block_scout_web/templates/address_validation/index.html.eex:24
#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 lib/block_scout_web/templates/chain/show.html.eex:180
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 lib/block_scout_web/templates/stakes/_table.html.eex:49
-#: lib/block_scout_web/templates/tokens/holder/index.html.eex:27 lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23
-#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:22 lib/block_scout_web/templates/tokens/transfer/index.html.eex:21
-#: lib/block_scout_web/templates/transaction/index.html.eex:29
+#: lib/block_scout_web/templates/tokens/holder/index.html.eex:27 lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23
+#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 lib/block_scout_web/templates/tokens/inventory/index.html.eex:22
+#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 lib/block_scout_web/templates/transaction/index.html.eex:29
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 lib/block_scout_web/templates/transaction_log/index.html.eex:15
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14
msgid "Something went wrong, click to reload."
@@ -2144,7 +2144,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:357
+#: lib/block_scout_web/views/transaction_view.ex:349
msgid "Success"
msgstr ""
@@ -2365,8 +2365,8 @@ msgid "There are no transactions."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28
-#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:26
+#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28
+#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 lib/block_scout_web/templates/tokens/transfer/index.html.eex:26
msgid "There are no transfers for this Token."
msgstr ""
@@ -2477,13 +2477,12 @@ msgid "Token"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
-#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6 lib/block_scout_web/views/transaction_view.ex:451
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 lib/block_scout_web/views/transaction_view.ex:443
msgid "Token Burning"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:452
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 lib/block_scout_web/views/transaction_view.ex:444
msgid "Token Creation"
msgstr ""
@@ -2495,25 +2494,25 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:20
+#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:9 lib/block_scout_web/views/tokens/overview_view.ex:42
msgid "Token Holders"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38
-#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37
msgid "Token ID"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
-#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8 lib/block_scout_web/views/transaction_view.ex:450
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 lib/block_scout_web/views/transaction_view.ex:442
msgid "Token Minting"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
-#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10 lib/block_scout_web/views/transaction_view.ex:453
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9
+#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 lib/block_scout_web/views/transaction_view.ex:445
msgid "Token Transfer"
msgstr ""
@@ -2523,8 +2522,8 @@ msgstr ""
#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:3
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 lib/block_scout_web/views/address_view.ex:348
-#: lib/block_scout_web/views/tokens/instance/overview_view.ex:177 lib/block_scout_web/views/tokens/overview_view.ex:41
-#: lib/block_scout_web/views/transaction_view.ex:514
+#: lib/block_scout_web/views/tokens/instance/overview_view.ex:178 lib/block_scout_web/views/tokens/overview_view.ex:41
+#: lib/block_scout_web/views/transaction_view.ex:506
msgid "Token Transfers"
msgstr ""
@@ -2620,7 +2619,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19
-#: lib/block_scout_web/views/transaction_view.ex:463
+#: lib/block_scout_web/views/transaction_view.ex:455
msgid "Transaction"
msgstr ""
@@ -2750,12 +2749,12 @@ msgid "Uncles"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:348
+#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Unconfirmed"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:6
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9
msgid "Unique Token"
msgstr ""
@@ -3191,3 +3190,13 @@ msgstr ""
#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:18
msgid "validator"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/views/transaction_view.ex:214
+msgid "ERC-1155 "
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11
+msgid "Not unique Token"
+msgstr ""
diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs
index 20bd85e7ef..6b5c6c853d 100644
--- a/apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/tokens/holder_view_test.exs
@@ -56,19 +56,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
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
index a772f615c5..15e9c843c0 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
@@ -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 """
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index d6ad755f3b..33426b98a9 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -4539,7 +4539,7 @@ defmodule Explorer.Chain do
nft_tokens =
from(
token in Token,
- where: token.type == ^"ERC-721",
+ where: token.type == ^"ERC-721" or token.type == ^"ERC-1155",
select: token.contract_address_hash
)
@@ -5693,6 +5693,13 @@ defmodule Explorer.Chain do
|> Repo.one() || Decimal.new(0)
end
+ # @spec fetch_last_token_balance_1155(Hash.Address.t(), Hash.Address.t()) :: Decimal.t()
+ def fetch_last_token_balance_1155(address_hash, token_contract_address_hash, token_id) do
+ address_hash
+ |> CurrentTokenBalance.last_token_balance_1155(token_contract_address_hash, token_id)
+ |> Repo.one() || Decimal.new(0)
+ end
+
@spec address_to_coin_balances(Hash.Address.t(), [paging_options]) :: []
def address_to_coin_balances(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@@ -5813,9 +5820,36 @@ defmodule Explorer.Chain do
|> Repo.all()
end
+ def fetch_token_holders_from_token_hash_and_token_id(contract_address_hash, token_id, options \\ []) do
+ contract_address_hash
+ |> CurrentTokenBalance.token_holders_1155_by_token_id(token_id, options)
+ |> Repo.all()
+ end
+
+ def token_id_1155_is_unique?(_, nil), do: false
+
+ def token_id_1155_is_unique?(contract_address_hash, token_id) do
+ result = contract_address_hash |> CurrentTokenBalance.token_balances_by_id_limit_2(token_id) |> Repo.all()
+
+ if length(result) == 1 do
+ Decimal.cmp(Enum.at(result, 0), 1) == :eq
+ else
+ false
+ end
+ end
+
+ def get_token_ids_1155(contract_address_hash) do
+ contract_address_hash
+ |> CurrentTokenBalance.token_ids_query()
+ |> Repo.all()
+ end
+
@spec count_token_holders_from_token_hash(Hash.Address.t()) :: non_neg_integer()
def count_token_holders_from_token_hash(contract_address_hash) do
- query = from(ctb in CurrentTokenBalance.token_holders_query(contract_address_hash), select: fragment("COUNT(*)"))
+ query =
+ from(ctb in CurrentTokenBalance.token_holders_query_for_count(contract_address_hash),
+ select: fragment("COUNT(DISTINCT(address_hash))")
+ )
Repo.one!(query, timeout: :infinity)
end
@@ -5853,7 +5887,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,
@@ -5900,10 +5934,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}]
@@ -5911,7 +5959,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
@@ -5927,7 +5975,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
@@ -5937,6 +5994,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
diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex
index db27975585..58d0b7a193 100644
--- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex
+++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex
@@ -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(),
@@ -30,15 +32,21 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
token: %Ecto.Association.NotLoaded{} | Token.t(),
token_contract_address_hash: Hash.Address,
block_number: Block.block_number(),
+ max_block_number: Block.block_number(),
inserted_at: DateTime.t(),
updated_at: DateTime.t(),
- value: Decimal.t() | nil
+ value: Decimal.t() | nil,
+ token_id: non_neg_integer() | nil,
+ token_type: String.t()
}
schema "address_current_token_balances" do
field(:value, :decimal)
field(:block_number, :integer)
+ field(:max_block_number, :integer, virtual: true)
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 +64,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
@@ -96,6 +104,57 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
|> offset(^offset)
end
+ @doc """
+ Builds an `Ecto.Query` to fetch the token holders from the given token contract address hash and token_id.
+
+ The Token Holders are the addresses that own a positive amount of the Token. So this query is
+ considering the following conditions:
+
+ * The token balance from the last block.
+ * Balances greater than 0.
+ * Excluding the burn address (0x0000000000000000000000000000000000000000).
+
+ """
+ def token_holders_1155_by_token_id(token_contract_address_hash, token_id, options \\ []) do
+ paging_options = Keyword.get(options, :paging_options, @default_paging_options)
+ offset = (max(paging_options.page_number, 1) - 1) * paging_options.page_size
+
+ token_contract_address_hash
+ |> token_holders_by_token_id_query(token_id)
+ |> preload(:address)
+ |> order_by([tb], desc: :value, desc: :address_hash)
+ |> Chain.page_token_balances(paging_options)
+ |> limit(^paging_options.page_size)
+ |> offset(^offset)
+ end
+
+ @doc """
+ Builds an `Ecto.Query` to fetch all available token_ids
+ """
+ def token_ids_query(token_contract_address_hash) do
+ from(
+ ctb in __MODULE__,
+ where: ctb.token_contract_address_hash == ^token_contract_address_hash,
+ where: ctb.address_hash != ^@burn_address_hash,
+ where: ctb.value > 0,
+ select: ctb.token_id,
+ distinct: ctb.token_id
+ )
+ end
+
+ @doc """
+ Builds an `Ecto.Query` to fetch all token holders, to count it
+ Used in `Explorer.Chain.count_token_holders_from_token_hash/1`
+ """
+ def token_holders_query_for_count(token_contract_address_hash) do
+ from(
+ ctb in __MODULE__,
+ where: ctb.token_contract_address_hash == ^token_contract_address_hash,
+ where: ctb.address_hash != ^@burn_address_hash,
+ where: ctb.value > 0
+ )
+ end
+
@doc """
Builds an `t:Ecto.Query.t/0` to fetch the current token balances of the given address.
"""
@@ -137,6 +196,48 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
)
end
+ @doc """
+ Builds an `t:Ecto.Query.t/0` to fetch the current balance of the given address for the given token and token_id
+ """
+ def last_token_balance_1155(address_hash, token_contract_address_hash, token_id) do
+ from(
+ ctb in __MODULE__,
+ where: ctb.token_contract_address_hash == ^token_contract_address_hash,
+ where: ctb.address_hash == ^address_hash,
+ where: ctb.token_id == ^token_id,
+ select: ctb.value
+ )
+ end
+
+ @doc """
+ Builds an `t:Ecto.Query.t/0` to check if the token_id corresponds to the unique token or not.
+ Used in `Explorer.Chain.token_id_1155_is_unique?/2`
+ """
+ def token_balances_by_id_limit_2(token_contract_address_hash, token_id) do
+ from(
+ ctb in __MODULE__,
+ where: ctb.token_contract_address_hash == ^token_contract_address_hash,
+ where: ctb.token_id == ^token_id,
+ where: ctb.address_hash != ^@burn_address_hash,
+ where: ctb.value > 0,
+ select: ctb.value,
+ limit: 2
+ )
+ end
+
+ @doc """
+ Builds an `t:Ecto.Query.t/0` to fetch holders of the particular token_id in ERC-1155
+ """
+ def token_holders_by_token_id_query(token_contract_address_hash, token_id) do
+ from(
+ ctb in __MODULE__,
+ where: ctb.token_contract_address_hash == ^token_contract_address_hash,
+ where: ctb.address_hash != ^@burn_address_hash,
+ where: ctb.value > 0,
+ where: ctb.token_id == ^token_id
+ )
+ end
+
@doc """
Builds an `t:Ecto.Query.t/0` to fetch addresses that hold the token.
diff --git a/apps/explorer/lib/explorer/chain/address/token.ex b/apps/explorer/lib/explorer/chain/address/token.ex
index 1dd167b779..b2bfabeaf2 100644
--- a/apps/explorer/lib/explorer/chain/address/token.ex
+++ b/apps/explorer/lib/explorer/chain/address/token.ex
@@ -34,17 +34,16 @@ defmodule Explorer.Chain.Address.Token do
address_hash
|> join_with_last_balance()
- |> order_filter_and_group()
+ |> filter_and_group()
+ |> order()
|> page_tokens(paging_options)
|> limit(^paging_options.page_size)
end
- defp order_filter_and_group(query) do
+ defp filter_and_group(query) do
from(
[token, balance] in query,
- order_by: fragment("? DESC, LOWER(?) ASC NULLS LAST", token.type, token.name),
where: balance.value > 0,
- group_by: [token.name, token.symbol, balance.value, token.type, token.contract_address_hash],
select: %Address.Token{
contract_address_hash: token.contract_address_hash,
inserted_at: max(token.inserted_at),
@@ -53,22 +52,40 @@ defmodule Explorer.Chain.Address.Token do
balance: balance.value,
decimals: max(token.decimals),
type: token.type
- }
+ },
+ group_by: [token.name, token.symbol, balance.value, token.type, token.contract_address_hash, balance.block_number]
+ )
+ end
+
+ defp order(query) do
+ from(
+ token in subquery(query),
+ order_by: fragment("? DESC, ? ASC NULLS LAST", token.type, token.name)
)
end
defp join_with_last_balance(address_hash) do
last_balance_query =
from(
- tb in CurrentTokenBalance,
- where: tb.address_hash == ^address_hash,
- select: %{value: tb.value, token_contract_address_hash: tb.token_contract_address_hash}
+ ctb in CurrentTokenBalance,
+ where: ctb.address_hash == ^address_hash,
+ select: %{
+ value: ctb.value,
+ token_contract_address_hash: ctb.token_contract_address_hash,
+ block_number: ctb.block_number,
+ max_block_number: over(max(ctb.block_number), :w)
+ },
+ windows: [
+ w: [partition_by: [ctb.token_contract_address_hash, ctb.address_hash]]
+ ]
)
from(
t in Chain.Token,
join: tb in subquery(last_balance_query),
- on: tb.token_contract_address_hash == t.contract_address_hash
+ on: tb.token_contract_address_hash == t.contract_address_hash,
+ where: tb.block_number == tb.max_block_number,
+ distinct: t.contract_address_hash
)
end
diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex
index d3941fd9e2..cc0d579da1 100644
--- a/apps/explorer/lib/explorer/chain/address/token_balance.ex
+++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex
@@ -23,6 +23,8 @@ defmodule Explorer.Chain.Address.TokenBalance 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(),
@@ -32,13 +34,17 @@ defmodule Explorer.Chain.Address.TokenBalance do
block_number: Block.block_number(),
inserted_at: DateTime.t(),
updated_at: DateTime.t(),
- value: Decimal.t() | nil
+ value: Decimal.t() | nil,
+ token_id: non_neg_integer() | nil,
+ token_type: String.t()
}
schema "address_token_balances" do
field(:value, :decimal)
field(:block_number, :integer)
field(:value_fetched_at, :utc_datetime_usec)
+ field(:token_id, :decimal)
+ field(:token_type, :string)
belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address)
@@ -53,8 +59,8 @@ defmodule Explorer.Chain.Address.TokenBalance 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
@@ -82,8 +88,9 @@ defmodule Explorer.Chain.Address.TokenBalance do
tb in TokenBalance,
join: t in Token,
on: tb.token_contract_address_hash == t.contract_address_hash,
- where: is_nil(tb.value_fetched_at) or is_nil(tb.value),
- where: (tb.address_hash != ^@burn_address_hash and t.type != "ERC-721") or t.type == "ERC-20"
+ where:
+ ((tb.address_hash != ^@burn_address_hash and t.type != "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155") and
+ (is_nil(tb.value_fetched_at) or is_nil(tb.value))
)
end
end
diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
index ce3a22a365..a04cd71932 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex
@@ -109,8 +109,16 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do
# Enforce ShareLocks tables order (see docs: sharelocks.md)
multi
|> Multi.run(:acquire_contract_address_tokens, fn repo, _ ->
- contract_address_hashes = changes_list |> Enum.map(& &1.token_contract_address_hash) |> Enum.uniq()
- Tokens.acquire_contract_address_tokens(repo, contract_address_hashes)
+ token_contract_address_hashes_and_ids =
+ changes_list
+ |> Enum.map(fn change ->
+ token_id = get_tokend_id(change)
+
+ {change.token_contract_address_hash, token_id}
+ end)
+ |> Enum.uniq()
+
+ Tokens.acquire_contract_address_tokens(repo, token_contract_address_hashes_and_ids)
end)
|> Multi.run(:address_current_token_balances, fn repo, _ ->
insert(repo, changes_list, insert_options)
@@ -131,6 +139,10 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do
end)
end
+ defp get_tokend_id(change) do
+ if Map.has_key?(change, :token_id), do: change.token_id, else: nil
+ end
+
@impl Import.Runner
def timeout, do: @timeout
@@ -198,21 +210,107 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options)
when is_atom(repo) and is_list(changes_list) do
+ inserted_changes_list =
+ insert_changes_list_with_and_without_token_id(changes_list, repo, timestamps, timeout, options)
+
+ {:ok, inserted_changes_list}
+ end
+
+ def insert_changes_list_with_and_without_token_id(changes_list, repo, timestamps, timeout, options) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
# Enforce CurrentTokenBalance ShareLocks order (see docs: sharelocks.md)
- ordered_changes_list = Enum.sort_by(changes_list, &{&1.token_contract_address_hash, &1.address_hash})
-
- Import.insert_changes_list(
- repo,
- ordered_changes_list,
- conflict_target: ~w(address_hash token_contract_address_hash)a,
- on_conflict: on_conflict,
- for: CurrentTokenBalance,
- returning: true,
- timeout: timeout,
- timestamps: timestamps
- )
+ %{
+ changes_list_no_token_id: changes_list_no_token_id,
+ changes_list_with_token_id: changes_list_with_token_id
+ } =
+ changes_list
+ |> Enum.reduce(%{changes_list_no_token_id: [], changes_list_with_token_id: []}, fn change, acc ->
+ updated_change =
+ if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do
+ change
+ else
+ Map.put(change, :token_id, nil)
+ end
+
+ if updated_change.token_id do
+ changes_list_with_token_id = [updated_change | acc.changes_list_with_token_id]
+
+ %{
+ changes_list_no_token_id: acc.changes_list_no_token_id,
+ changes_list_with_token_id: changes_list_with_token_id
+ }
+ else
+ changes_list_no_token_id = [updated_change | acc.changes_list_no_token_id]
+
+ %{
+ changes_list_no_token_id: changes_list_no_token_id,
+ changes_list_with_token_id: acc.changes_list_with_token_id
+ }
+ end
+ end)
+
+ ordered_changes_list_no_token_id =
+ changes_list_no_token_id
+ |> Enum.group_by(fn %{
+ address_hash: address_hash,
+ token_contract_address_hash: token_contract_address_hash
+ } ->
+ {address_hash, token_contract_address_hash}
+ end)
+ |> Enum.map(fn {_, grouped_address_token_balances} ->
+ Enum.max_by(grouped_address_token_balances, fn %{block_number: block_number} -> block_number end)
+ end)
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash})
+
+ ordered_changes_list_with_token_id =
+ changes_list_with_token_id
+ |> Enum.group_by(fn %{
+ address_hash: address_hash,
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id
+ } ->
+ {address_hash, token_contract_address_hash, token_id}
+ end)
+ |> Enum.map(fn {_, grouped_address_token_balances} ->
+ Enum.max_by(grouped_address_token_balances, fn %{block_number: block_number} -> block_number end)
+ end)
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash})
+
+ {:ok, inserted_changes_list_no_token_id} =
+ if Enum.count(ordered_changes_list_no_token_id) > 0 do
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list_no_token_id,
+ conflict_target: {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash) WHERE token_id IS NULL>},
+ on_conflict: on_conflict,
+ for: CurrentTokenBalance,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+ else
+ {:ok, []}
+ end
+
+ {:ok, inserted_changes_list_with_token_id} =
+ if Enum.count(ordered_changes_list_with_token_id) > 0 do
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list_with_token_id,
+ conflict_target:
+ {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, token_id) WHERE token_id IS NOT NULL>},
+ on_conflict: on_conflict,
+ for: CurrentTokenBalance,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+ else
+ {:ok, []}
+ end
+
+ inserted_changes_list_no_token_id ++ inserted_changes_list_with_token_id
end
defp default_on_conflict do
diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex
index 8189a5845c..66c3b3da88 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex
@@ -60,20 +60,113 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
# Enforce TokenBalance ShareLocks order (see docs: sharelocks.md)
- ordered_changes_list =
- Enum.sort_by(changes_list, &{&1.token_contract_address_hash, &1.address_hash, &1.block_number})
-
- {:ok, _} =
- Import.insert_changes_list(
- repo,
- ordered_changes_list,
- conflict_target: ~w(address_hash token_contract_address_hash block_number)a,
- on_conflict: on_conflict,
- for: TokenBalance,
- returning: true,
- timeout: timeout,
- timestamps: timestamps
- )
+ %{
+ changes_list_no_token_id: changes_list_no_token_id,
+ changes_list_with_token_id: changes_list_with_token_id
+ } =
+ changes_list
+ |> Enum.reduce(%{changes_list_no_token_id: [], changes_list_with_token_id: []}, fn change, acc ->
+ updated_change =
+ if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do
+ change
+ else
+ Map.put(change, :token_id, nil)
+ end
+
+ if updated_change.token_id do
+ changes_list_with_token_id = [updated_change | acc.changes_list_with_token_id]
+
+ %{
+ changes_list_no_token_id: acc.changes_list_no_token_id,
+ changes_list_with_token_id: changes_list_with_token_id
+ }
+ else
+ changes_list_no_token_id = [updated_change | acc.changes_list_no_token_id]
+
+ %{
+ changes_list_no_token_id: changes_list_no_token_id,
+ changes_list_with_token_id: acc.changes_list_with_token_id
+ }
+ end
+ end)
+
+ ordered_changes_list_no_token_id =
+ changes_list_no_token_id
+ |> Enum.group_by(fn %{
+ address_hash: address_hash,
+ token_contract_address_hash: token_contract_address_hash,
+ block_number: block_number
+ } ->
+ {token_contract_address_hash, address_hash, block_number}
+ end)
+ |> Enum.map(fn {_, grouped_address_token_balances} ->
+ dedup = Enum.dedup(grouped_address_token_balances)
+
+ if Enum.count(dedup) > 1 do
+ Enum.max_by(dedup, fn %{value_fetched_at: value_fetched_at} -> value_fetched_at end)
+ else
+ Enum.at(dedup, 0)
+ end
+ end)
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash, &1.block_number})
+
+ ordered_changes_list_with_token_id =
+ changes_list_with_token_id
+ |> Enum.group_by(fn %{
+ address_hash: address_hash,
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id,
+ block_number: block_number
+ } ->
+ {token_contract_address_hash, token_id, address_hash, block_number}
+ end)
+ |> Enum.map(fn {_, grouped_address_token_balances} ->
+ if Enum.count(grouped_address_token_balances) > 1 do
+ Enum.max_by(grouped_address_token_balances, fn %{value_fetched_at: value_fetched_at} -> value_fetched_at end)
+ else
+ Enum.at(grouped_address_token_balances, 0)
+ end
+ end)
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash, &1.block_number})
+
+ {:ok, inserted_changes_list_no_token_id} =
+ if Enum.count(ordered_changes_list_no_token_id) > 0 do
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list_no_token_id,
+ conflict_target:
+ {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, block_number) WHERE token_id IS NULL>},
+ on_conflict: on_conflict,
+ for: TokenBalance,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+ else
+ {:ok, []}
+ end
+
+ {:ok, inserted_changes_list_with_token_id} =
+ if Enum.count(ordered_changes_list_with_token_id) > 0 do
+ Import.insert_changes_list(
+ repo,
+ ordered_changes_list_with_token_id,
+ conflict_target:
+ {:unsafe_fragment,
+ ~s<(address_hash, token_contract_address_hash, token_id, block_number) WHERE token_id IS NOT NULL>},
+ on_conflict: on_conflict,
+ for: TokenBalance,
+ returning: true,
+ timeout: timeout,
+ timestamps: timestamps
+ )
+ else
+ {:ok, []}
+ end
+
+ inserted_changes_list = inserted_changes_list_no_token_id ++ inserted_changes_list_with_token_id
+
+ {:ok, inserted_changes_list}
end
defp default_on_conflict do
diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex
index b8d67208ce..b74cedf13c 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex
@@ -119,13 +119,13 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
query =
from(ctb in Address.CurrentTokenBalance,
where: ctb.block_number in ^consensus_block_numbers,
- select: ctb.token_contract_address_hash,
- distinct: ctb.token_contract_address_hash
+ select: {ctb.token_contract_address_hash, ctb.token_id},
+ distinct: [ctb.token_contract_address_hash, ctb.token_id]
)
- contract_address_hashes = repo.all(query)
+ contract_address_hashes_and_token_ids = repo.all(query)
- Tokens.acquire_contract_address_tokens(repo, contract_address_hashes)
+ Tokens.acquire_contract_address_tokens(repo, contract_address_hashes_and_token_ids)
end
defp fork_transactions(%{
@@ -341,10 +341,11 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
ordered_query =
from(tb in Address.TokenBalance,
where: tb.block_number in ^consensus_block_numbers,
- select: map(tb, [:address_hash, :token_contract_address_hash, :block_number]),
+ select: map(tb, [:address_hash, :token_contract_address_hash, :token_id, :block_number]),
# Enforce TokenBalance ShareLocks order (see docs: sharelocks.md)
order_by: [
tb.token_contract_address_hash,
+ tb.token_id,
tb.address_hash,
tb.block_number
],
@@ -359,6 +360,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
ordered_address_token_balance.address_hash == tb.address_hash and
ordered_address_token_balance.token_contract_address_hash ==
tb.token_contract_address_hash and
+ ((is_nil(ordered_address_token_balance.token_id) and is_nil(tb.token_id)) or
+ (ordered_address_token_balance.token_id == tb.token_id and
+ not is_nil(ordered_address_token_balance.token_id) and not is_nil(tb.token_id))) and
ordered_address_token_balance.block_number == tb.block_number
)
@@ -378,10 +382,11 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
ordered_query =
from(ctb in Address.CurrentTokenBalance,
where: ctb.block_number in ^consensus_block_numbers,
- select: map(ctb, [:address_hash, :token_contract_address_hash]),
+ select: map(ctb, [:address_hash, :token_contract_address_hash, :token_id]),
# Enforce CurrentTokenBalance ShareLocks order (see docs: sharelocks.md)
order_by: [
ctb.token_contract_address_hash,
+ ctb.token_id,
ctb.address_hash
],
lock: "FOR UPDATE"
@@ -393,6 +398,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
map(ctb, [
:address_hash,
:token_contract_address_hash,
+ :token_id,
# Used to determine if `address_hash` was a holder of `token_contract_address_hash` before
# `address_current_token_balance` is deleted in `update_tokens_holder_count`.
@@ -401,8 +407,10 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
inner_join: ordered_address_current_token_balance in subquery(ordered_query),
on:
ordered_address_current_token_balance.address_hash == ctb.address_hash and
- ordered_address_current_token_balance.token_contract_address_hash ==
- ctb.token_contract_address_hash
+ ordered_address_current_token_balance.token_contract_address_hash == ctb.token_contract_address_hash and
+ ((is_nil(ordered_address_current_token_balance.token_id) and is_nil(ctb.token_id)) or
+ (ordered_address_current_token_balance.token_id == ctb.token_id and
+ not is_nil(ordered_address_current_token_balance.token_id) and not is_nil(ctb.token_id)))
)
try do
@@ -417,31 +425,13 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
defp derive_address_current_token_balances(_, [], _), do: {:ok, []}
- defp derive_address_current_token_balances(repo, deleted_address_current_token_balances, %{timeout: timeout})
+ defp derive_address_current_token_balances(
+ repo,
+ deleted_address_current_token_balances,
+ %{timeout: timeout} = options
+ )
when is_list(deleted_address_current_token_balances) do
- initial_query =
- from(tb in Address.TokenBalance,
- select: %{
- address_hash: tb.address_hash,
- token_contract_address_hash: tb.token_contract_address_hash,
- block_number: max(tb.block_number)
- },
- group_by: [tb.address_hash, tb.token_contract_address_hash]
- )
-
- final_query =
- Enum.reduce(deleted_address_current_token_balances, initial_query, fn %{
- address_hash: address_hash,
- token_contract_address_hash:
- token_contract_address_hash
- },
- acc_query ->
- from(tb in acc_query,
- or_where:
- tb.address_hash == ^address_hash and
- tb.token_contract_address_hash == ^token_contract_address_hash
- )
- end)
+ final_query = derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances)
new_current_token_balance_query =
from(new_current_token_balance in subquery(final_query),
@@ -449,42 +439,82 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
on:
tb.address_hash == new_current_token_balance.address_hash and
tb.token_contract_address_hash == new_current_token_balance.token_contract_address_hash and
+ ((is_nil(tb.token_id) and is_nil(new_current_token_balance.token_id)) or
+ (tb.token_id == new_current_token_balance.token_id and
+ not is_nil(tb.token_id) and not is_nil(new_current_token_balance.token_id))) and
tb.block_number == new_current_token_balance.block_number,
select: %{
address_hash: new_current_token_balance.address_hash,
token_contract_address_hash: new_current_token_balance.token_contract_address_hash,
+ token_id: new_current_token_balance.token_id,
block_number: new_current_token_balance.block_number,
value: tb.value,
inserted_at: over(min(tb.inserted_at), :w),
updated_at: over(max(tb.updated_at), :w)
},
windows: [
- w: [partition_by: [tb.address_hash, tb.token_contract_address_hash]]
+ w: [partition_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id]]
]
)
- ordered_current_token_balance =
+ current_token_balance =
new_current_token_balance_query
|> repo.all()
- # Enforce CurrentTokenBalance ShareLocks order (see docs: sharelocks.md)
- |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash})
- {_total, result} =
- repo.insert_all(
- Address.CurrentTokenBalance,
- ordered_current_token_balance,
- # No `ON CONFLICT` because `delete_address_current_token_balances`
- # should have removed any conflicts.
- returning: [:address_hash, :token_contract_address_hash, :block_number, :value],
- timeout: timeout
+ timestamps = Import.timestamps()
+
+ result =
+ CurrentTokenBalances.insert_changes_list_with_and_without_token_id(
+ current_token_balance,
+ repo,
+ timestamps,
+ timeout,
+ options
)
derived_address_current_token_balances =
- Enum.map(result, &Map.take(&1, [:address_hash, :token_contract_address_hash, :block_number, :value]))
+ Enum.map(result, &Map.take(&1, [:address_hash, :token_contract_address_hash, :token_id, :block_number, :value]))
{:ok, derived_address_current_token_balances}
end
+ defp derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) do
+ initial_query =
+ from(tb in Address.TokenBalance,
+ select: %{
+ address_hash: tb.address_hash,
+ token_contract_address_hash: tb.token_contract_address_hash,
+ token_id: tb.token_id,
+ block_number: max(tb.block_number)
+ },
+ group_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id]
+ )
+
+ Enum.reduce(deleted_address_current_token_balances, initial_query, fn %{
+ address_hash: address_hash,
+ token_contract_address_hash:
+ token_contract_address_hash,
+ token_id: token_id
+ },
+ acc_query ->
+ if token_id do
+ from(tb in acc_query,
+ or_where:
+ tb.address_hash == ^address_hash and
+ tb.token_contract_address_hash == ^token_contract_address_hash and
+ tb.token_id == ^token_id
+ )
+ else
+ from(tb in acc_query,
+ or_where:
+ tb.address_hash == ^address_hash and
+ tb.token_contract_address_hash == ^token_contract_address_hash and
+ is_nil(tb.token_id)
+ )
+ end
+ end)
+ end
+
# `block_rewards` are linked to `blocks.hash`, but fetched by `blocks.number`, so when a block with the same number is
# inserted, the old block rewards need to be deleted, so that the old and new rewards aren't combined.
defp delete_rewards(repo, blocks_changes, %{timeout: timeout}) do
diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex
index d6a254911c..1e24d15d29 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex
@@ -21,17 +21,70 @@ defmodule Explorer.Chain.Import.Runner.Tokens do
@type holder_count :: non_neg_integer()
@type token_holder_count :: %{contract_address_hash: Hash.Address.t(), count: holder_count()}
- def acquire_contract_address_tokens(repo, contract_address_hashes) do
- token_query =
- from(
- token in Token,
- where: token.contract_address_hash in ^contract_address_hashes,
- # Enforce Token ShareLocks order (see docs: sharelocks.md)
- order_by: token.contract_address_hash,
- lock: "FOR UPDATE"
+ def acquire_contract_address_tokens(repo, contract_address_hashes_and_token_ids) do
+ initial_query_no_token_id =
+ from(token in Token,
+ select: token
)
- tokens = repo.all(token_query)
+ initial_query_with_token_id =
+ from(token in Token,
+ left_join: instance in Token.Instance,
+ on: token.contract_address_hash == instance.token_contract_address_hash,
+ select: token
+ )
+
+ {query_no_token_id, query_with_token_id} =
+ contract_address_hashes_and_token_ids
+ |> Enum.reduce({initial_query_no_token_id, initial_query_with_token_id}, fn {contract_address_hash, token_id},
+ {query_no_token_id,
+ query_with_token_id} ->
+ if is_nil(token_id) do
+ {from(
+ token in query_no_token_id,
+ or_where: token.contract_address_hash == ^contract_address_hash
+ ), query_with_token_id}
+ else
+ {query_no_token_id,
+ from(
+ [token, instance] in query_with_token_id,
+ or_where: token.contract_address_hash == ^contract_address_hash and instance.token_id == ^token_id
+ )}
+ end
+ end)
+
+ final_query_no_token_id =
+ if query_no_token_id == initial_query_no_token_id do
+ nil
+ else
+ from(
+ token in query_no_token_id,
+ # Enforce Token ShareLocks order (see docs: sharelocks.md)
+ order_by: [
+ token.contract_address_hash
+ ],
+ lock: "FOR UPDATE"
+ )
+ end
+
+ final_query_with_token_id =
+ if query_with_token_id == initial_query_with_token_id do
+ nil
+ else
+ from(
+ [token, instance] in query_with_token_id,
+ # Enforce Token ShareLocks order (see docs: sharelocks.md)
+ order_by: [
+ token.contract_address_hash,
+ instance.token_id
+ ],
+ lock: "FOR UPDATE"
+ )
+ end
+
+ tokens_no_token_id = (final_query_no_token_id && repo.all(final_query_no_token_id)) || []
+ tokens_with_token_id = (final_query_with_token_id && repo.all(final_query_with_token_id)) || []
+ tokens = tokens_no_token_id ++ tokens_with_token_id
{:ok, tokens}
end
diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex
index 990ac1fa34..686ee7c70f 100644
--- a/apps/explorer/lib/explorer/chain/token.ex
+++ b/apps/explorer/lib/explorer/chain/token.ex
@@ -8,6 +8,7 @@ defmodule Explorer.Chain.Token do
* ERC-20
* ERC-721
+ * ERC-1155
## Token Specifications
diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex
index 29a93c5074..34ab5076c0 100644
--- a/apps/explorer/lib/explorer/chain/token_transfer.ex
+++ b/apps/explorer/lib/explorer/chain/token_transfer.ex
@@ -47,9 +47,11 @@ defmodule Explorer.Chain.TokenTransfer do
* `:transaction` - The `t:Explorer.Chain.Transaction.t/0` ledger
* `:transaction_hash` - Transaction foreign key
* `:log_index` - Index of the corresponding `t:Explorer.Chain.Log.t/0` in the transaction.
+ * `:amounts` - Tokens transferred amounts in case of batched transfer in ERC-1155
+ * `:token_ids` - IDs of the tokens (applicable to ERC-1155 tokens)
"""
@type t :: %TokenTransfer{
- amount: Decimal.t(),
+ amount: Decimal.t() | nil,
block_number: non_neg_integer() | nil,
block_hash: Hash.Full.t(),
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
@@ -61,12 +63,16 @@ defmodule Explorer.Chain.TokenTransfer do
token_id: non_neg_integer() | nil,
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.Full.t(),
- log_index: non_neg_integer()
+ log_index: non_neg_integer(),
+ amounts: [Decimal.t()] | nil,
+ token_ids: [non_neg_integer()] | nil
}
@typep paging_options :: {:paging_options, PagingOptions.t()}
@constant "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+ @erc1155_single_transfer_signature "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"
+ @erc1155_batch_transfer_signature "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb"
@transfer_function_signature "0xa9059cbb"
@@ -76,6 +82,8 @@ defmodule Explorer.Chain.TokenTransfer do
field(:block_number, :integer)
field(:log_index, :integer, primary_key: true)
field(:token_id, :decimal)
+ field(:amounts, {:array, :decimal})
+ field(:token_ids, {:array, :decimal})
belongs_to(:from_address, Address, foreign_key: :from_address_hash, references: :hash, type: Hash.Address)
belongs_to(:to_address, Address, foreign_key: :to_address_hash, references: :hash, type: Hash.Address)
@@ -115,7 +123,7 @@ defmodule Explorer.Chain.TokenTransfer do
end
@required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash block_hash)a
- @optional_attrs ~w(amount token_id)a
+ @optional_attrs ~w(amount token_id amounts token_ids)a
@doc false
def changeset(%TokenTransfer{} = struct, params \\ %{}) do
@@ -134,6 +142,10 @@ defmodule Explorer.Chain.TokenTransfer do
"""
def constant, do: @constant
+ def erc1155_single_transfer_signature, do: @erc1155_single_transfer_signature
+
+ def erc1155_batch_transfer_signature, do: @erc1155_batch_transfer_signature
+
@doc """
ERC 20's transfer(address,uint256) function signature
"""
@@ -294,6 +306,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],
diff --git a/apps/explorer/lib/explorer/token/balance_reader.ex b/apps/explorer/lib/explorer/token/balance_reader.ex
index 22a45fec54..0a093c8cd7 100644
--- a/apps/explorer/lib/explorer/token/balance_reader.ex
+++ b/apps/explorer/lib/explorer/token/balance_reader.ex
@@ -27,6 +27,18 @@ defmodule Explorer.Token.BalanceReader do
}
]
+ @erc1155_balance_function_abi [
+ %{
+ "constant" => true,
+ "inputs" => [%{"name" => "_owner", "type" => "address"}, %{"name" => "_id", "type" => "uint256"}],
+ "name" => "balanceOf",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
@spec get_balances_of([
%{token_contract_address_hash: String.t(), address_hash: String.t(), block_number: non_neg_integer()}
]) :: [{:ok, non_neg_integer()} | {:error, String.t()}]
@@ -37,6 +49,31 @@ defmodule Explorer.Token.BalanceReader do
|> Enum.map(&format_balance_result/1)
end
+ @spec get_balances_of_with_abi(
+ [
+ %{token_contract_address_hash: String.t(), address_hash: String.t(), block_number: non_neg_integer()}
+ ],
+ [%{}]
+ ) :: [{:ok, non_neg_integer()} | {:error, String.t()}]
+ def get_balances_of_with_abi(token_balance_requests, abi) do
+ formatted_balances_requests =
+ if abi == @erc1155_balance_function_abi do
+ token_balance_requests
+ |> Enum.map(&format_erc_1155_balance_request/1)
+ else
+ token_balance_requests
+ |> Enum.map(&format_balance_request/1)
+ end
+
+ if Enum.count(formatted_balances_requests) > 0 do
+ formatted_balances_requests
+ |> Reader.query_contracts(abi)
+ |> Enum.map(&format_balance_result/1)
+ else
+ []
+ end
+ end
+
defp format_balance_request(%{
address_hash: address_hash,
block_number: block_number,
@@ -50,6 +87,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
diff --git a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
index e9c6c06ad3..7b87543e6f 100644
--- a/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
+++ b/apps/explorer/lib/explorer/token/instance_metadata_retriever.ex
@@ -29,9 +29,36 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
}
]
+ @uri "0e89341c"
+
+ @abi_uri [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [
+ %{
+ "type" => "string",
+ "name" => "",
+ "internalType" => "string"
+ }
+ ],
+ "name" => "uri",
+ "inputs" => [
+ %{
+ "type" => "uint256",
+ "name" => "_id",
+ "internalType" => "uint256"
+ }
+ ],
+ "constant" => true
+ }
+ ]
+
@cryptokitties_address_hash "0x06012c8cf97bead5deae237070f9587f8e7a266d"
@no_uri_error "no uri"
+ @vm_execution_error "VM execution error"
def fetch_metadata(unquote(@cryptokitties_address_hash), token_id) do
%{"tokenURI" => {:ok, ["https://api.cryptokitties.co/kitties/#{token_id}"]}}
@@ -42,31 +69,62 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
# c87b56dd = keccak256(tokenURI(uint256))
contract_functions = %{@token_uri => [token_id]}
- contract_address_hash
- |> query_contract(contract_functions)
- |> fetch_json()
+ res =
+ contract_address_hash
+ |> query_contract(contract_functions, @abi)
+ |> fetch_json()
+
+ if res == {:ok, %{error: @vm_execution_error}} do
+ contract_functions_uri = %{@uri => [token_id]}
+
+ contract_address_hash
+ |> query_contract(contract_functions_uri, @abi_uri)
+ |> fetch_json()
+ else
+ res
+ end
end
- def query_contract(contract_address_hash, contract_functions) do
- Reader.query_contract(contract_address_hash, @abi, contract_functions)
+ def query_contract(contract_address_hash, contract_functions, abi) do
+ Reader.query_contract(contract_address_hash, abi, contract_functions)
end
- def fetch_json(%{@token_uri => {:ok, [""]}}) do
+ def fetch_json(uri) when uri in [%{@token_uri => {:ok, [""]}}, %{@uri => {:ok, [""]}}] do
{:ok, %{error: @no_uri_error}}
end
- def fetch_json(%{@token_uri => {:error, "(-32015) VM execution error."}}) do
- {:ok, %{error: @no_uri_error}}
+ def fetch_json(uri)
+ when uri in [
+ %{@token_uri => {:error, "(-32015) VM execution error."}},
+ %{@uri => {:error, "(-32015) VM execution error."}}
+ ] do
+ {:ok, %{error: @vm_execution_error}}
+ end
+
+ def fetch_json(%{@token_uri => {:error, "(-32015) VM execution error." <> _}}) do
+ {:ok, %{error: @vm_execution_error}}
+ end
+
+ def fetch_json(%{@uri => {:error, "(-32015) VM execution error." <> _}}) do
+ {:ok, %{error: @vm_execution_error}}
end
def fetch_json(%{@token_uri => {:ok, ["http://" <> _ = token_uri]}}) do
fetch_metadata(token_uri)
end
+ def fetch_json(%{@uri => {:ok, ["http://" <> _ = token_uri]}}) do
+ fetch_metadata(token_uri)
+ end
+
def fetch_json(%{@token_uri => {:ok, ["https://" <> _ = token_uri]}}) do
fetch_metadata(token_uri)
end
+ def fetch_json(%{@uri => {:ok, ["https://" <> _ = token_uri]}}) do
+ fetch_metadata(token_uri)
+ end
+
def fetch_json(%{@token_uri => {:ok, ["data:application/json," <> json]}}) do
decoded_json = URI.decode(json)
@@ -80,16 +138,39 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
{:error, json}
end
+ def fetch_json(%{@uri => {:ok, ["data:application/json," <> json]}}) do
+ decoded_json = URI.decode(json)
+
+ fetch_json(%{@token_uri => {:ok, [decoded_json]}})
+ rescue
+ e ->
+ Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"],
+ fetcher: :token_instances
+ )
+
+ {:error, json}
+ end
+
def fetch_json(%{@token_uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}) do
ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid
fetch_metadata(ipfs_url)
end
+ def fetch_json(%{@uri => {:ok, ["ipfs://ipfs/" <> ipfs_uid]}}) do
+ ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid
+ fetch_metadata(ipfs_url)
+ end
+
def fetch_json(%{@token_uri => {:ok, ["ipfs://" <> ipfs_uid]}}) do
ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid
fetch_metadata(ipfs_url)
end
+ def fetch_json(%{@uri => {:ok, ["ipfs://" <> ipfs_uid]}}) do
+ ipfs_url = "https://ipfs.io/ipfs/" <> ipfs_uid
+ fetch_metadata(ipfs_url)
+ end
+
def fetch_json(%{@token_uri => {:ok, [json]}}) do
{:ok, json} = decode_json(json)
@@ -103,17 +184,30 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
{:error, json}
end
+ def fetch_json(%{@uri => {:ok, [json]}}) do
+ {:ok, json} = decode_json(json)
+
+ check_type(json)
+ rescue
+ e ->
+ Logger.debug(["Unknown metadata format #{inspect(json)}. error #{inspect(e)}"],
+ fetcher: :token_instances
+ )
+
+ {:error, json}
+ end
+
def fetch_json(result) do
Logger.debug(["Unknown metadata format #{inspect(result)}."], fetcher: :token_instances)
{:error, result}
end
- defp fetch_metadata(token_uri) do
- case HTTPoison.get(token_uri) do
+ defp fetch_metadata(uri) do
+ case HTTPoison.get(uri) do
{:ok, %Response{body: body, status_code: 200, headers: headers}} ->
if Enum.member?(headers, {"Content-Type", "image/png"}) do
- json = %{"image" => token_uri}
+ json = %{"image" => uri}
check_type(json)
else
@@ -135,7 +229,7 @@ defmodule Explorer.Token.InstanceMetadataRetriever do
end
rescue
e ->
- Logger.debug(["Could not send request to token uri #{inspect(token_uri)}. error #{inspect(e)}"],
+ Logger.debug(["Could not send request to token uri #{inspect(uri)}. error #{inspect(e)}"],
fetcher: :token_instances
)
diff --git a/apps/explorer/priv/repo/migrations/20200214152058_add_token_id_to_token_balances.exs b/apps/explorer/priv/repo/migrations/20200214152058_add_token_id_to_token_balances.exs
new file mode 100644
index 0000000000..b009091f81
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20200214152058_add_token_id_to_token_balances.exs
@@ -0,0 +1,12 @@
+defmodule Explorer.Repo.Migrations.AddTokenIdToTokenBalances do
+ use Ecto.Migration
+
+ def change do
+ alter table(:address_token_balances) do
+ add(:token_id, :numeric, precision: 78, scale: 0, null: true)
+ add(:token_type, :string, null: true)
+ end
+
+ create(index(:address_token_balances, [:token_id]))
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20210422115740_add_token_id_to_current_token_balances.exs b/apps/explorer/priv/repo/migrations/20210422115740_add_token_id_to_current_token_balances.exs
new file mode 100644
index 0000000000..4d8d870529
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20210422115740_add_token_id_to_current_token_balances.exs
@@ -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
diff --git a/apps/explorer/priv/repo/migrations/20210423084253_address_current_token_balances_add_token_id_to_unique_index.exs b/apps/explorer/priv/repo/migrations/20210423084253_address_current_token_balances_add_token_id_to_unique_index.exs
new file mode 100644
index 0000000000..b71de0448e
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20210423084253_address_current_token_balances_add_token_id_to_unique_index.exs
@@ -0,0 +1,25 @@
+defmodule Explorer.Repo.Migrations.AddressCurrentTokenBalancesAddTokenIdToUniqueIndex do
+ use Ecto.Migration
+
+ def change do
+ drop(unique_index(:address_current_token_balances, ~w(address_hash token_contract_address_hash)a))
+
+ create(
+ unique_index(
+ :address_current_token_balances,
+ ~w(address_hash token_contract_address_hash token_id)a,
+ name: :fetched_current_token_balances_with_token_id,
+ where: "token_id IS NOT NULL"
+ )
+ )
+
+ create(
+ unique_index(
+ :address_current_token_balances,
+ ~w(address_hash token_contract_address_hash)a,
+ name: :fetched_current_token_balances,
+ where: "token_id IS NULL"
+ )
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20210423091652_address_token_balances_add_token_id_to_unique_index.exs b/apps/explorer/priv/repo/migrations/20210423091652_address_token_balances_add_token_id_to_unique_index.exs
new file mode 100644
index 0000000000..1968f52132
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20210423091652_address_token_balances_add_token_id_to_unique_index.exs
@@ -0,0 +1,25 @@
+defmodule Explorer.Repo.Migrations.AddressTokenBalancesAddTokenIdToUniqueIndex do
+ use Ecto.Migration
+
+ def change do
+ drop(unique_index(:address_token_balances, ~w(address_hash token_contract_address_hash block_number)a))
+
+ create(
+ unique_index(
+ :address_token_balances,
+ ~w(address_hash token_contract_address_hash token_id block_number)a,
+ name: :fetched_token_balances_with_token_id,
+ where: "token_id IS NOT NULL"
+ )
+ )
+
+ create(
+ unique_index(
+ :address_token_balances,
+ ~w(address_hash token_contract_address_hash block_number)a,
+ name: :fetched_token_balances,
+ where: "token_id IS NULL"
+ )
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20210423094801_address_token_balances_change_unfetched_token_balances_unique_index.exs b/apps/explorer/priv/repo/migrations/20210423094801_address_token_balances_change_unfetched_token_balances_unique_index.exs
new file mode 100644
index 0000000000..911a7affbb
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20210423094801_address_token_balances_change_unfetched_token_balances_unique_index.exs
@@ -0,0 +1,32 @@
+defmodule Explorer.Repo.Migrations.AddressTokenBalancesChangeUnfetchedTokenBalancesUniqueIndex do
+ use Ecto.Migration
+
+ def change do
+ drop(
+ unique_index(
+ :address_token_balances,
+ ~w(address_hash token_contract_address_hash block_number)a,
+ name: :unfetched_token_balances,
+ where: "value_fetched_at IS NULL"
+ )
+ )
+
+ create(
+ unique_index(
+ :address_token_balances,
+ ~w(address_hash token_contract_address_hash block_number)a,
+ name: :unfetched_token_balances,
+ where: "value_fetched_at IS NULL and token_id IS NULL"
+ )
+ )
+
+ create(
+ unique_index(
+ :address_token_balances,
+ ~w(address_hash token_contract_address_hash token_id block_number)a,
+ name: :unfetched_token_balances_with_token_id,
+ where: "value_fetched_at IS NULL and token_id IS NOT NULL"
+ )
+ )
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20210423115108_extend_token_transfers_for_erc1155.exs b/apps/explorer/priv/repo/migrations/20210423115108_extend_token_transfers_for_erc1155.exs
new file mode 100644
index 0000000000..211524e954
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20210423115108_extend_token_transfers_for_erc1155.exs
@@ -0,0 +1,10 @@
+defmodule Explorer.Repo.Migrations.ExtendTokenTransfersForErc1155 do
+ use Ecto.Migration
+
+ def change do
+ alter table(:token_transfers) do
+ add(:amounts, {:array, :decimal}, null: true)
+ add(:token_ids, {:array, :numeric}, precision: 78, scale: 0, null: true)
+ end
+ end
+end
diff --git a/apps/explorer/priv/repo/migrations/20211013190346_remove_duplicates_of_current_token_balances.exs b/apps/explorer/priv/repo/migrations/20211013190346_remove_duplicates_of_current_token_balances.exs
new file mode 100644
index 0000000000..ec26db4b73
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20211013190346_remove_duplicates_of_current_token_balances.exs
@@ -0,0 +1,55 @@
+defmodule Explorer.Repo.Migrations.RemoveDuplicatesOfCurrentTokenBalances do
+ use Ecto.Migration
+
+ def change do
+ execute("""
+ DELETE FROM address_current_token_balances
+ WHERE id in (
+ SELECT a.id FROM (SELECT actb.*
+ FROM address_current_token_balances actb
+ INNER JOIN tokens t
+ ON actb.token_contract_address_hash = t.contract_address_hash
+ WHERE t.type='ERC-721') AS a
+ LEFT JOIN
+ (SELECT actb.address_hash, actb.token_contract_address_hash, MAX(actb.value_fetched_at) AS max_value_fetched_at
+ FROM address_current_token_balances actb
+ INNER JOIN tokens t
+ ON actb.token_contract_address_hash = t.contract_address_hash
+ WHERE t.type='ERC-721'
+ GROUP BY token_contract_address_hash, address_hash) c
+ ON a.address_hash=c.address_hash AND a.token_contract_address_hash = c.token_contract_address_hash AND a.value_fetched_at = c.max_value_fetched_at
+ WHERE c.address_hash IS NULL
+ );
+ """)
+
+ execute("""
+ UPDATE address_current_token_balances
+ SET token_id = NULL
+ WHERE id in (
+ SELECT a.id FROM (SELECT actb.*
+ FROM address_current_token_balances actb
+ INNER JOIN tokens t
+ ON actb.token_contract_address_hash = t.contract_address_hash
+ WHERE t.type='ERC-721'
+ AND actb.token_id IS NOT NULL
+ ) a
+ );
+ """)
+
+ execute("""
+ UPDATE address_current_token_balances
+ SET token_type = t.type
+ FROM tokens t
+ WHERE address_current_token_balances.token_type IS NULL
+ AND t.contract_address_hash = address_current_token_balances.token_contract_address_hash;
+ """)
+
+ execute("""
+ UPDATE address_token_balances
+ SET token_type = t.type
+ FROM tokens t
+ WHERE address_token_balances.token_type IS NULL
+ AND t.contract_address_hash = address_token_balances.token_contract_address_hash;
+ """)
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs
index e7dcfe4b06..7c34c72719 100644
--- a/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/address/current_token_balances_test.exs
@@ -65,6 +65,141 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalancesTest do
assert current_token_balances == 1
end
+ test "inserts values for multiple token IDs in the current token balances", %{
+ address: %Address{hash: address_hash},
+ token: %Token{contract_address_hash: token_contract_address_hash},
+ options: options
+ } do
+ value_1 = Decimal.new(111)
+ token_id_1 = Decimal.new(1)
+
+ value_2 = Decimal.new(222)
+ token_id_2 = Decimal.new(2)
+
+ token_erc_20 = insert(:token, holder_count: 0)
+ token_erc_20_contract_address_hash = token_erc_20.contract_address_hash
+ value_3 = Decimal.new(333)
+ token_id_3 = nil
+
+ token_erc_721 = insert(:token, holder_count: 0)
+ token_erc_721_contract_address_hash = token_erc_721.contract_address_hash
+ value_4 = Decimal.new(1)
+ token_id_4 = Decimal.new(1)
+
+ value_5 = Decimal.new(2)
+ token_id_5 = Decimal.new(555)
+
+ block_number = 1
+
+ assert {:ok,
+ %{
+ address_current_token_balances: [
+ %Explorer.Chain.Address.CurrentTokenBalance{
+ address_hash: ^address_hash,
+ block_number: ^block_number,
+ token_contract_address_hash: ^token_erc_20_contract_address_hash,
+ value: ^value_3,
+ token_id: ^token_id_3
+ },
+ %Explorer.Chain.Address.CurrentTokenBalance{
+ address_hash: ^address_hash,
+ block_number: ^block_number,
+ token_contract_address_hash: ^token_erc_721_contract_address_hash,
+ value: ^value_5,
+ token_id: nil
+ },
+ %Explorer.Chain.Address.CurrentTokenBalance{
+ address_hash: ^address_hash,
+ block_number: ^block_number,
+ token_contract_address_hash: ^token_contract_address_hash,
+ value: ^value_1,
+ token_id: ^token_id_1
+ },
+ %Explorer.Chain.Address.CurrentTokenBalance{
+ address_hash: ^address_hash,
+ block_number: ^block_number,
+ token_contract_address_hash: ^token_contract_address_hash,
+ value: ^value_2,
+ token_id: ^token_id_2
+ }
+ ],
+ address_current_token_balances_update_token_holder_counts: [
+ %{
+ contract_address_hash: ^token_contract_address_hash,
+ holder_count: 2
+ },
+ %{
+ contract_address_hash: ^token_erc_20_contract_address_hash,
+ holder_count: 1
+ },
+ %{
+ contract_address_hash: ^token_erc_721_contract_address_hash,
+ holder_count: 1
+ }
+ ]
+ }} =
+ run_changes_list(
+ [
+ %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_contract_address_hash,
+ value: value_1,
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id_1,
+ token_type: "ERC-1155"
+ },
+ %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_contract_address_hash,
+ value: value_2,
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id_2,
+ token_type: "ERC-1155"
+ },
+ %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_erc_20.contract_address_hash,
+ value: value_3,
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id_3,
+ token_type: "ERC-20"
+ },
+ %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_erc_721.contract_address_hash,
+ value: value_4,
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id_4,
+ token_type: "ERC-721"
+ },
+ %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_erc_721.contract_address_hash,
+ value: value_5,
+ value_fetched_at: DateTime.utc_now(),
+ token_id: token_id_5,
+ token_type: "ERC-721"
+ }
+ ],
+ options
+ )
+
+ current_token_balances =
+ CurrentTokenBalance
+ |> Repo.all()
+
+ current_token_balances_count =
+ current_token_balances
+ |> Enum.count()
+
+ assert current_token_balances_count == 4
+ end
+
test "updates when the new block number is greater", %{
address: address,
token: token,
diff --git a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
index a35fbcc578..39e40c7e73 100644
--- a/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
+++ b/apps/explorer/test/explorer/chain/import/runner/address/token_balances_test.exs
@@ -29,7 +29,9 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do
block_number: block_number,
token_contract_address_hash: token_contract_address_hash,
value: value,
- value_fetched_at: value_fetched_at
+ value_fetched_at: value_fetched_at,
+ token_id: 11,
+ token_type: "ERC-20"
}
assert {:ok,
@@ -69,7 +71,9 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do
block_number: block_number,
token_contract_address_hash: token_contract_address_hash,
value: nil,
- value_fetched_at: value_fetched_at
+ value_fetched_at: value_fetched_at,
+ token_id: nil,
+ token_type: "ERC-20"
}
assert {:ok,
@@ -97,6 +101,58 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalancesTest do
end
end
+ test "does not nillifies existing value ERC-1155" do
+ address = insert(:address)
+ token = insert(:token)
+
+ options = %{
+ timeout: :infinity,
+ timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}
+ }
+
+ value_fetched_at = DateTime.utc_now()
+
+ block_number = 1
+
+ value = Decimal.new(100)
+
+ token_contract_address_hash = token.contract_address_hash
+ address_hash = address.hash
+
+ changes = %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_contract_address_hash,
+ value: nil,
+ value_fetched_at: value_fetched_at,
+ token_id: 11,
+ token_type: "ERC-1155"
+ }
+
+ assert {:ok,
+ %{
+ address_token_balances: [
+ %TokenBalance{
+ address_hash: address_hash,
+ block_number: ^block_number,
+ token_contract_address_hash: ^token_contract_address_hash,
+ value: nil,
+ value_fetched_at: ^value_fetched_at
+ }
+ ]
+ }} = run_changes(changes, options)
+
+ new_changes = %{
+ address_hash: address_hash,
+ block_number: block_number,
+ token_contract_address_hash: token_contract_address_hash,
+ value: value,
+ value_fetched_at: DateTime.utc_now()
+ }
+
+ run_changes(new_changes, options)
+ end
+
defp run_changes(changes, options) when is_map(changes) do
run_changes_list([changes], options)
end
diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs
index f96a536526..9cde0dbb81 100644
--- a/apps/explorer/test/explorer/chain/import_test.exs
+++ b/apps/explorer/test/explorer/chain/import_test.exs
@@ -375,17 +375,20 @@ defmodule Explorer.Chain.ImportTest do
%{
address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
- block_number: "37"
+ block_number: "37",
+ token_type: "ERC-20"
},
%{
address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
- block_number: "37"
+ block_number: "37",
+ token_type: "ERC-20"
},
%{
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
token_contract_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
- block_number: "37"
+ block_number: "37",
+ token_type: "ERC-20"
}
],
timeout: 5
@@ -425,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
@@ -1564,8 +1569,8 @@ defmodule Explorer.Chain.ImportTest do
},
address_coin_balances: %{
params: [
- %{address_hash: miner_hash, block_number: block_number, value: nil},
- %{address_hash: uncle_miner_hash, block_number: block_number, value: nil}
+ %{address_hash: miner_hash, block_number: block_number, value: nil, token_type: "ERC-20"},
+ %{address_hash: uncle_miner_hash, block_number: block_number, value: nil, token_type: "ERC-20"}
],
timeout: 1
},
@@ -2250,7 +2255,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"
}
]
},
@@ -2260,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"
}
]
},
diff --git a/apps/explorer/test/explorer/token/balance_reader_test.exs b/apps/explorer/test/explorer/token/balance_reader_test.exs
index 1a98dd2455..126e903cde 100644
--- a/apps/explorer/test/explorer/token/balance_reader_test.exs
+++ b/apps/explorer/test/explorer/token/balance_reader_test.exs
@@ -32,7 +32,8 @@ defmodule Explorer.Token.BalanceReaderTest do
%{
token_contract_address_hash: token_contract_address_hash,
address_hash: address_hash,
- block_number: block_number
+ block_number: block_number,
+ token_type: "ERC-20"
}
])
@@ -51,7 +52,8 @@ defmodule Explorer.Token.BalanceReaderTest do
%{
token_contract_address_hash: token_contract_address_hash,
address_hash: address_hash,
- block_number: block_number
+ block_number: block_number,
+ token_type: "ERC-20"
}
])
diff --git a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs
index eec3b38e49..3afe5c937f 100644
--- a/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs
+++ b/apps/explorer/test/explorer/token/instance_metadata_retriever_test.exs
@@ -9,6 +9,49 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
setup :verify_on_exit!
setup :set_mox_global
+ @abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [
+ %{"type" => "string", "name" => ""}
+ ],
+ "name" => "tokenURI",
+ "inputs" => [
+ %{
+ "type" => "uint256",
+ "name" => "_tokenId"
+ }
+ ],
+ "constant" => true
+ }
+ ]
+
+ @abi_uri [
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [
+ %{
+ "type" => "string",
+ "name" => "",
+ "internalType" => "string"
+ }
+ ],
+ "name" => "uri",
+ "inputs" => [
+ %{
+ "type" => "uint256",
+ "name" => "_id",
+ "internalType" => "uint256"
+ }
+ ],
+ "constant" => true
+ }
+ ]
+
describe "fetch_metadata/2" do
@tag :no_parity
@tag :no_geth
@@ -46,9 +89,56 @@ defmodule Explorer.Token.InstanceMetadataRetrieverTest do
assert %{
"c87b56dd" => {:ok, ["https://vault.warriders.com/18290729947667102496.json"]}
} ==
- InstanceMetadataRetriever.query_contract("0x5caebd3b32e210e85ce3e9d51638b9c445481567", %{
- "c87b56dd" => [18_290_729_947_667_102_496]
- })
+ InstanceMetadataRetriever.query_contract(
+ "0x5caebd3b32e210e85ce3e9d51638b9c445481567",
+ %{
+ "c87b56dd" => [18_290_729_947_667_102_496]
+ },
+ @abi
+ )
+ end
+
+ test "fetches json metadata for ERC-1155 token", %{json_rpc_named_arguments: json_rpc_named_arguments} do
+ if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
+ EthereumJSONRPC.Mox
+ |> expect(:json_rpc, fn [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ method: "eth_call",
+ params: [
+ %{
+ data:
+ "0x0e89341c000000000000000000000000000000000000000000000000fdd5b9fa9d4bfb20",
+ to: "0x5caebd3b32e210e85ce3e9d51638b9c445481567"
+ },
+ "latest"
+ ]
+ }
+ ],
+ _options ->
+ {:ok,
+ [
+ %{
+ id: 0,
+ jsonrpc: "2.0",
+ result:
+ "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003568747470733a2f2f7661756c742e7761727269646572732e636f6d2f31383239303732393934373636373130323439362e6a736f6e0000000000000000000000"
+ }
+ ]}
+ end)
+ end
+
+ assert %{
+ "0e89341c" => {:ok, ["https://vault.warriders.com/18290729947667102496.json"]}
+ } ==
+ InstanceMetadataRetriever.query_contract(
+ "0x5caebd3b32e210e85ce3e9d51638b9c445481567",
+ %{
+ "0e89341c" => [18_290_729_947_667_102_496]
+ },
+ @abi_uri
+ )
end
end
diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex
index b76b2438e4..63ed106510 100644
--- a/apps/explorer/test/support/factory.ex
+++ b/apps/explorer/test/support/factory.ex
@@ -586,7 +586,8 @@ defmodule Explorer.Factory do
token_contract_address_hash: insert(:token).contract_address_hash,
block_number: block_number(),
value: Enum.random(1..100_000),
- value_fetched_at: DateTime.utc_now()
+ value_fetched_at: DateTime.utc_now(),
+ token_type: "ERC-20"
}
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_balance.ex b/apps/indexer/lib/indexer/fetcher/token_balance.ex
index fe32ac1943..1f34d3108c 100644
--- a/apps/indexer/lib/indexer/fetcher/token_balance.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_balance.ex
@@ -96,7 +96,7 @@ defmodule Indexer.Fetcher.TokenBalance do
retryable_params_list =
params_list
|> Enum.filter(&(&1.retries_count <= @max_retries))
- |> Enum.uniq_by(&Map.take(&1, [:token_contract_address_hash, :address_hash, :block_number]))
+ |> Enum.uniq_by(&Map.take(&1, [:token_contract_address_hash, :token_id, :address_hash, :block_number]))
Logger.metadata(count: Enum.count(retryable_params_list))
@@ -138,15 +138,26 @@ defmodule Indexer.Fetcher.TokenBalance do
%{
token_contract_address_hash: token_contract_address_hash,
address_hash: address_hash,
- block_number: block_number
+ block_number: block_number,
+ token_type: token_type,
+ token_id: token_id
} = token_balance
) do
retries_count = Map.get(token_balance, :retries_count, 0)
- {address_hash.bytes, token_contract_address_hash.bytes, block_number, retries_count}
+ token_id_int =
+ case token_id do
+ %Decimal{} -> Decimal.to_integer(token_id)
+ id_int when is_integer(id_int) -> id_int
+ _ -> token_id
+ end
+
+ {address_hash.bytes, token_contract_address_hash.bytes, block_number, token_type, token_id_int, retries_count}
end
- defp format_params({address_hash_bytes, token_contract_address_hash_bytes, block_number, retries_count}) do
+ defp format_params(
+ {address_hash_bytes, token_contract_address_hash_bytes, block_number, token_type, token_id, retries_count}
+ ) do
{:ok, token_contract_address_hash} = Hash.Address.cast(token_contract_address_hash_bytes)
{:ok, address_hash} = Hash.Address.cast(address_hash_bytes)
@@ -154,7 +165,9 @@ defmodule Indexer.Fetcher.TokenBalance do
token_contract_address_hash: to_string(token_contract_address_hash),
address_hash: to_string(address_hash),
block_number: block_number,
- retries_count: retries_count
+ retries_count: retries_count,
+ token_type: token_type,
+ token_id: token_id
}
end
end
diff --git a/apps/indexer/lib/indexer/token_balances.ex b/apps/indexer/lib/indexer/token_balances.ex
index 9a10dad13c..3e60f23260 100644
--- a/apps/indexer/lib/indexer/token_balances.ex
+++ b/apps/indexer/lib/indexer/token_balances.ex
@@ -13,6 +13,18 @@ defmodule Indexer.TokenBalances do
alias Indexer.Fetcher.TokenBalance
alias Indexer.Tracer
+ @erc1155_balance_function_abi [
+ %{
+ "constant" => true,
+ "inputs" => [%{"name" => "_owner", "type" => "address"}, %{"name" => "_id", "type" => "uint256"}],
+ "name" => "balanceOf",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
@doc """
Fetches TokenBalances from specific Addresses and Blocks in the Blockchain
@@ -26,6 +38,8 @@ defmodule Indexer.TokenBalances do
* `token_contract_address_hash` - The contract address that represents the Token in the blockchain.
* `address_hash` - The address_hash that we want to know the balance.
* `block_number` - The block number that the address_hash has the balance.
+ * `token_type` - type of the token that balance belongs to
+ * `token_id` - token id for ERC-1155 tokens
"""
def fetch_token_balances_from_blockchain([]), do: {:ok, []}
@@ -33,12 +47,39 @@ defmodule Indexer.TokenBalances do
def fetch_token_balances_from_blockchain(token_balances) do
Logger.debug("fetching token balances", count: Enum.count(token_balances))
- requested_token_balances =
+ regular_token_balances =
+ token_balances
+ |> Enum.filter(fn request ->
+ if Map.has_key?(request, :token_type) do
+ request.token_type !== "ERC-1155"
+ else
+ true
+ end
+ end)
+
+ erc1155_token_balances =
token_balances
+ |> Enum.filter(fn request ->
+ if Map.has_key?(request, :token_type) do
+ request.token_type == "ERC-1155"
+ else
+ false
+ end
+ end)
+
+ requested_regular_token_balances =
+ regular_token_balances
|> BalanceReader.get_balances_of()
- |> Stream.zip(token_balances)
+ |> Stream.zip(regular_token_balances)
|> Enum.map(fn {result, token_balance} -> set_token_balance_value(result, token_balance) end)
+ requested_erc1155_token_balances =
+ erc1155_token_balances
+ |> BalanceReader.get_balances_of_with_abi(@erc1155_balance_function_abi)
+ |> Stream.zip(erc1155_token_balances)
+ |> Enum.map(fn {result, token_balance} -> set_token_balance_value(result, token_balance) end)
+
+ requested_token_balances = requested_regular_token_balances ++ requested_erc1155_token_balances
fetched_token_balances = Enum.filter(requested_token_balances, &ignore_request_with_errors/1)
requested_token_balances
@@ -51,13 +92,17 @@ defmodule Indexer.TokenBalances do
def to_address_current_token_balances(address_token_balances) when is_list(address_token_balances) do
address_token_balances
- |> Enum.group_by(fn %{address_hash: address_hash, token_contract_address_hash: token_contract_address_hash} ->
- {address_hash, token_contract_address_hash}
+ |> Enum.group_by(fn %{
+ address_hash: address_hash,
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id
+ } ->
+ {address_hash, token_contract_address_hash, token_id}
end)
|> Enum.map(fn {_, grouped_address_token_balances} ->
Enum.max_by(grouped_address_token_balances, fn %{block_number: block_number} -> block_number end)
end)
- |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash})
+ |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash})
end
defp set_token_balance_value({:ok, balance}, token_balance) do
@@ -137,10 +182,20 @@ defmodule Indexer.TokenBalances do
end
defp present?(list, token_balance) do
- Enum.any?(list, fn item ->
- token_balance.address_hash == item.address_hash &&
- token_balance.token_contract_address_hash == item.token_contract_address_hash &&
- token_balance.block_number == item.block_number
- end)
+ if token_balance.token_id do
+ Enum.any?(list, fn item ->
+ token_balance.address_hash == item.address_hash &&
+ token_balance.token_contract_address_hash == item.token_contract_address_hash &&
+ token_balance.token_id == item.token_id &&
+ token_balance.block_number == item.block_number
+ end)
+ else
+ Enum.any?(list, fn item ->
+ token_balance.address_hash == item.address_hash &&
+ token_balance.token_contract_address_hash == item.token_contract_address_hash &&
+ is_nil(item.token_id) &&
+ token_balance.block_number == item.block_number
+ end)
+ end
end
end
diff --git a/apps/indexer/lib/indexer/transform/address_token_balances.ex b/apps/indexer/lib/indexer/transform/address_token_balances.ex
index ff87ba7d57..ab6bb02fee 100644
--- a/apps/indexer/lib/indexer/transform/address_token_balances.ex
+++ b/apps/indexer/lib/indexer/transform/address_token_balances.ex
@@ -16,14 +16,25 @@ defmodule Indexer.Transform.AddressTokenBalances do
block_number: block_number,
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
- token_contract_address_hash: token_contract_address_hash
- },
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: token_id,
+ token_type: token_type
+ } = params,
acc
when is_integer(block_number) and is_binary(from_address_hash) and
is_binary(to_address_hash) and is_binary(token_contract_address_hash) ->
- acc
- |> add_token_balance_address(from_address_hash, token_contract_address_hash, block_number)
- |> add_token_balance_address(to_address_hash, token_contract_address_hash, block_number)
+ if params[:token_ids] && token_type == "ERC-1155" do
+ params[:token_ids]
+ |> Enum.reduce(acc, fn id, sub_acc ->
+ sub_acc
+ |> add_token_balance_address(from_address_hash, token_contract_address_hash, id, token_type, block_number)
+ |> add_token_balance_address(to_address_hash, token_contract_address_hash, id, token_type, block_number)
+ end)
+ else
+ acc
+ |> add_token_balance_address(from_address_hash, token_contract_address_hash, token_id, token_type, block_number)
+ |> add_token_balance_address(to_address_hash, token_contract_address_hash, token_id, token_type, block_number)
+ end
end)
end
@@ -31,13 +42,15 @@ defmodule Indexer.Transform.AddressTokenBalances do
Enum.filter(token_transfers_params, &do_filter_burn_address/1)
end
- defp add_token_balance_address(map_set, unquote(@burn_address), _, _), do: map_set
+ defp add_token_balance_address(map_set, unquote(@burn_address), _, _, _, _), do: map_set
- defp add_token_balance_address(map_set, address, token_contract_address, block_number) do
+ defp add_token_balance_address(map_set, address, token_contract_address, token_id, token_type, block_number) do
MapSet.put(map_set, %{
address_hash: address,
token_contract_address_hash: token_contract_address,
- block_number: block_number
+ block_number: block_number,
+ token_id: token_id,
+ token_type: token_type
})
end
diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex
index ade12ef893..3d5ea4ed02 100644
--- a/apps/indexer/lib/indexer/transform/token_transfers.ex
+++ b/apps/indexer/lib/indexer/transform/token_transfers.ex
@@ -18,12 +18,21 @@ defmodule Indexer.Transform.TokenTransfers do
def parse(logs) do
initial_acc = %{tokens: [], token_transfers: []}
- token_transfers_from_logs =
+ erc20_and_erc721_token_transfers =
logs
|> Enum.filter(&(&1.first_topic == unquote(TokenTransfer.constant())))
|> Enum.reduce(initial_acc, &do_parse/2)
- token_transfers = token_transfers_from_logs.token_transfers
+ erc1155_token_transfers =
+ logs
+ |> Enum.filter(fn log ->
+ log.first_topic == TokenTransfer.erc1155_single_transfer_signature() ||
+ log.first_topic == TokenTransfer.erc1155_batch_transfer_signature()
+ end)
+ |> Enum.reduce(initial_acc, &do_parse(&1, &2, :erc1155))
+
+ tokens = erc1155_token_transfers.tokens ++ erc20_and_erc721_token_transfers.tokens
+ token_transfers = erc1155_token_transfers.token_transfers ++ erc20_and_erc721_token_transfers.token_transfers
token_transfers
|> Enum.filter(fn token_transfer ->
@@ -35,18 +44,23 @@ defmodule Indexer.Transform.TokenTransfers do
|> Enum.dedup()
|> Enum.each(&update_token/1)
- tokens_dedup = token_transfers_from_logs.tokens |> Enum.dedup()
+ tokens_dedup = tokens |> Enum.dedup()
token_transfers_from_logs_dedup = %{
tokens: tokens_dedup,
- token_transfers: token_transfers_from_logs.token_transfers
+ token_transfers: token_transfers
}
token_transfers_from_logs_dedup
end
- defp do_parse(log, %{tokens: tokens, token_transfers: token_transfers} = acc) do
- {token, token_transfer} = parse_params(log)
+ defp do_parse(log, %{tokens: tokens, token_transfers: token_transfers} = acc, type \\ :erc20_erc721) do
+ {token, token_transfer} =
+ if type != :erc1155 do
+ parse_params(log)
+ else
+ parse_erc1155_params(log)
+ end
%{
tokens: [token | tokens],
@@ -72,6 +86,7 @@ defmodule Indexer.Transform.TokenTransfers do
to_address_hash: truncate_address_hash(log.third_topic),
token_contract_address_hash: log.address_hash,
transaction_hash: log.transaction_hash,
+ token_id: nil,
token_type: "ERC-20"
}
@@ -109,7 +124,14 @@ defmodule Indexer.Transform.TokenTransfers do
end
# ERC-721 token transfer with info in data field instead of in log topics
- defp parse_params(%{second_topic: nil, third_topic: nil, fourth_topic: nil, data: data} = log)
+ defp parse_params(
+ %{
+ second_topic: nil,
+ third_topic: nil,
+ fourth_topic: nil,
+ data: data
+ } = log
+ )
when not is_nil(data) do
[from_address_hash, to_address_hash, token_id] = decode_data(data, [:address, :address, {:uint, 256}])
@@ -157,6 +179,62 @@ defmodule Indexer.Transform.TokenTransfers do
:ok
end
+ def parse_erc1155_params(
+ %{
+ first_topic: unquote(TokenTransfer.erc1155_batch_transfer_signature()),
+ third_topic: third_topic,
+ fourth_topic: fourth_topic,
+ data: data
+ } = log
+ ) do
+ [token_ids, values] = decode_data(data, [{:array, {:uint, 256}}, {:array, {:uint, 256}}])
+
+ token_transfer = %{
+ block_number: log.block_number,
+ block_hash: log.block_hash,
+ log_index: log.index,
+ from_address_hash: truncate_address_hash(third_topic),
+ to_address_hash: truncate_address_hash(fourth_topic),
+ token_contract_address_hash: log.address_hash,
+ transaction_hash: log.transaction_hash,
+ token_type: "ERC-1155",
+ token_ids: token_ids,
+ token_id: nil,
+ amounts: values
+ }
+
+ token = %{
+ contract_address_hash: log.address_hash,
+ type: "ERC-1155"
+ }
+
+ {token, token_transfer}
+ end
+
+ def parse_erc1155_params(%{third_topic: third_topic, fourth_topic: fourth_topic, data: data} = log) do
+ [token_id, value] = decode_data(data, [{:uint, 256}, {:uint, 256}])
+
+ token_transfer = %{
+ amount: value,
+ block_number: log.block_number,
+ block_hash: log.block_hash,
+ log_index: log.index,
+ from_address_hash: truncate_address_hash(third_topic),
+ to_address_hash: truncate_address_hash(fourth_topic),
+ token_contract_address_hash: log.address_hash,
+ transaction_hash: log.transaction_hash,
+ token_type: "ERC-1155",
+ token_id: token_id
+ }
+
+ token = %{
+ contract_address_hash: log.address_hash,
+ type: "ERC-1155"
+ }
+
+ {token, token_transfer}
+ end
+
defp truncate_address_hash(nil), do: "0x0000000000000000000000000000000000000000"
defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do
diff --git a/apps/indexer/test/indexer/fetcher/token_balance_test.exs b/apps/indexer/test/indexer/fetcher/token_balance_test.exs
index 2ecee3eacd..f5879aac6b 100644
--- a/apps/indexer/test/indexer/fetcher/token_balance_test.exs
+++ b/apps/indexer/test/indexer/fetcher/token_balance_test.exs
@@ -17,13 +17,13 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
%Address.TokenBalance{
address_hash: %Hash{bytes: address_hash_bytes},
token_contract_address_hash: %Hash{bytes: token_contract_address_hash_bytes},
- block_number: block_number
+ block_number: _block_number
} = insert(:token_balance, block_number: 1_000, value_fetched_at: nil)
insert(:token_balance, value_fetched_at: DateTime.utc_now())
assert TokenBalance.init([], &[&1 | &2], nil) == [
- {address_hash_bytes, token_contract_address_hash_bytes, block_number, 0}
+ {address_hash_bytes, token_contract_address_hash_bytes, 1000, "ERC-20", nil, 0}
]
end
end
@@ -58,7 +58,7 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
)
assert TokenBalance.run(
- [{address_hash_bytes, token_contract_address_hash_bytes, block_number, 0}],
+ [{address_hash_bytes, token_contract_address_hash_bytes, block_number, "ERC-20", nil, 0}],
nil
) == :ok
@@ -76,26 +76,12 @@ 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,
token_balance_a.token_contract_address_hash.bytes,
+ "ERC-20",
+ nil,
token_balance_a.block_number,
# this token balance must be ignored
max_retries
@@ -103,6 +89,8 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
{
token_balance_b.address_hash.bytes,
token_balance_b.token_contract_address_hash.bytes,
+ "ERC-20",
+ nil,
token_balance_b.block_number,
# this token balance still have to be retried
max_retries - 2
@@ -136,8 +124,8 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
assert TokenBalance.run(
[
- {address_hash_bytes, token_contract_address_hash_bytes, block_number, 0},
- {address_hash_bytes, token_contract_address_hash_bytes, block_number, 0}
+ {address_hash_bytes, token_contract_address_hash_bytes, block_number, "ERC-20", nil, 0},
+ {address_hash_bytes, token_contract_address_hash_bytes, block_number, "ERC-20", nil, 0}
],
nil
) == :ok
@@ -161,6 +149,7 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
address_hash: nil,
block_number: nil,
token_contract_address_hash: to_string(token_balance.token_contract_address_hash),
+ token_id: nil,
value: nil,
value_fetched_at: nil
}
@@ -177,7 +166,9 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
%{
address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
block_number: 19999,
- token_contract_address_hash: to_string(contract.contract_address_hash)
+ token_contract_address_hash: to_string(contract.contract_address_hash),
+ token_type: "ERC-20",
+ token_id: nil
}
]
@@ -186,7 +177,32 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
assert {:ok, _} = Explorer.Chain.hash_to_address(address_hash)
end
- test "import the token balances and return :ok when there are multiple balances for the same address on the batch" do
+ test "import the token balances and return :ok when there are multiple balances for the same address on the batch (ERC-20)" do
+ contract = insert(:token)
+ contract2 = insert(:token)
+ insert(:block, number: 19999)
+
+ token_balances_params = [
+ %{
+ address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+ block_number: 19999,
+ token_contract_address_hash: to_string(contract.contract_address_hash),
+ token_id: nil,
+ token_type: "ERC-20"
+ },
+ %{
+ address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+ block_number: 19999,
+ token_contract_address_hash: to_string(contract2.contract_address_hash),
+ token_id: nil,
+ token_type: "ERC-20"
+ }
+ ]
+
+ assert TokenBalance.import_token_balances(token_balances_params) == :ok
+ end
+
+ test "import the token balances and return :ok when there are multiple balances for the same address on the batch (ERC-1155)" do
contract = insert(:token)
contract2 = insert(:token)
insert(:block, number: 19999)
@@ -195,12 +211,16 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
%{
address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
block_number: 19999,
- token_contract_address_hash: to_string(contract.contract_address_hash)
+ token_contract_address_hash: to_string(contract.contract_address_hash),
+ token_id: 11,
+ token_type: "ERC-20"
},
%{
address_hash: "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
block_number: 19999,
- token_contract_address_hash: to_string(contract2.contract_address_hash)
+ token_contract_address_hash: to_string(contract2.contract_address_hash),
+ token_id: 11,
+ token_type: "ERC-1155"
}
]
diff --git a/apps/indexer/test/indexer/token_balances_test.exs b/apps/indexer/test/indexer/token_balances_test.exs
index 6c2eccf373..8752012387 100644
--- a/apps/indexer/test/indexer/token_balances_test.exs
+++ b/apps/indexer/test/indexer/token_balances_test.exs
@@ -26,10 +26,14 @@ defmodule Indexer.TokenBalancesTest do
token = insert(:token, contract_address: build(:contract_address))
address_hash_string = Hash.to_string(address.hash)
+ token_contract_address_hash = Hash.to_string(token.contract_address_hash)
+
data = %{
- token_contract_address_hash: Hash.to_string(token.contract_address_hash),
+ token_contract_address_hash: token_contract_address_hash,
address_hash: address_hash_string,
- block_number: 1_000
+ block_number: 1_000,
+ token_id: 11,
+ token_type: "ERC-20"
}
get_balance_from_blockchain()
@@ -38,13 +42,183 @@ defmodule Indexer.TokenBalancesTest do
assert %{
value: 1_000_000_000_000_000_000_000_000,
- token_contract_address_hash: token_contract_address_hash,
- address_hash: address_hash,
+ token_contract_address_hash: ^token_contract_address_hash,
+ address_hash: ^address_hash_string,
block_number: 1_000,
value_fetched_at: _
} = List.first(result)
end
+ test "fetches balances of ERC-1155 tokens" do
+ address = insert(:address, hash: "0x609991ca0ae39bc4eaf2669976237296d40c2f31")
+
+ address_hash_string = Hash.to_string(address.hash)
+
+ token_contract_address_hash = "0xf7f79032fd395978acb7069c74d21e5a53206559"
+
+ contract_address = insert(:address, hash: token_contract_address_hash)
+
+ token = insert(:token, contract_address: contract_address)
+
+ data = [
+ %{
+ token_contract_address_hash: Hash.to_string(token.contract_address_hash),
+ address_hash: address_hash_string,
+ block_number: 1_000,
+ token_id: 5,
+ token_type: "ERC-1155"
+ }
+ ]
+
+ get_erc1155_balance_from_blockchain()
+
+ {:ok, result} = TokenBalances.fetch_token_balances_from_blockchain(data)
+
+ assert [
+ %{
+ value: 2,
+ token_contract_address_hash: ^token_contract_address_hash,
+ address_hash: ^address_hash_string,
+ block_number: 1_000,
+ value_fetched_at: _
+ }
+ ] = result
+ end
+
+ test "fetches multiple balances of tokens" do
+ address_1 = insert(:address, hash: "0xecba3c9ea993b0e0594e0b0a0d361a1f9596e310")
+ address_2 = insert(:address, hash: "0x609991ca0ae39bc4eaf2669976237296d40c2f31")
+ address_3 = insert(:address, hash: "0xf712a82dd8e2ac923299193e9d6daeda2d5a32fd")
+
+ address_1_hash_string = Hash.to_string(address_1.hash)
+ address_2_hash_string = Hash.to_string(address_2.hash)
+ address_3_hash_string = Hash.to_string(address_3.hash)
+
+ token_1_contract_address_hash = "0x57e93bb58268de818b42e3795c97bad58afcd3fe"
+ token_2_contract_address_hash = "0xe0d0b1dbbcf3dd5cac67edaf9243863fd70745da"
+ token_3_contract_address_hash = "0x22c1f6050e56d2876009903609a2cc3fef83b415"
+ token_4_contract_address_hash = "0xf7f79032fd395978acb7069c74d21e5a53206559"
+
+ contract_address_1 = insert(:address, hash: token_1_contract_address_hash)
+ contract_address_2 = insert(:address, hash: token_2_contract_address_hash)
+ contract_address_3 = insert(:address, hash: token_3_contract_address_hash)
+ contract_address_4 = insert(:address, hash: token_4_contract_address_hash)
+
+ token_1 = insert(:token, contract_address: contract_address_1)
+ token_2 = insert(:token, contract_address: contract_address_2)
+ token_3 = insert(:token, contract_address: contract_address_3)
+ token_4 = insert(:token, contract_address: contract_address_4)
+
+ data = [
+ %{
+ token_contract_address_hash: Hash.to_string(token_1.contract_address_hash),
+ address_hash: address_1_hash_string,
+ block_number: 1_000,
+ token_id: nil,
+ token_type: "ERC-20"
+ },
+ %{
+ token_contract_address_hash: Hash.to_string(token_2.contract_address_hash),
+ address_hash: address_2_hash_string,
+ block_number: 1_000,
+ token_id: nil,
+ token_type: "ERC-20"
+ },
+ %{
+ token_contract_address_hash: Hash.to_string(token_3.contract_address_hash),
+ address_hash: address_2_hash_string,
+ block_number: 1_000,
+ token_id: 42,
+ token_type: "ERC-721"
+ },
+ %{
+ token_contract_address_hash: Hash.to_string(token_4.contract_address_hash),
+ address_hash: address_2_hash_string,
+ block_number: 1_000,
+ token_id: 5,
+ token_type: "ERC-1155"
+ },
+ %{
+ token_contract_address_hash: Hash.to_string(token_2.contract_address_hash),
+ address_hash: Hash.to_string(token_2.contract_address_hash),
+ block_number: 1_000,
+ token_id: nil,
+ token_type: "ERC-20"
+ },
+ %{
+ token_contract_address_hash: Hash.to_string(token_2.contract_address_hash),
+ address_hash: address_3_hash_string,
+ block_number: 1_000,
+ token_id: nil,
+ token_type: "ERC-20"
+ },
+ %{
+ token_contract_address_hash: Hash.to_string(token_2.contract_address_hash),
+ address_hash: Hash.to_string(token_2.contract_address_hash),
+ block_number: 1_000,
+ token_id: nil,
+ token_type: "ERC-20"
+ }
+ ]
+
+ get_multiple_balances_from_blockchain()
+ get_erc1155_balance_from_blockchain()
+
+ {:ok, result} = TokenBalances.fetch_token_balances_from_blockchain(data)
+
+ assert [
+ %{
+ value: 1_000_000_000_000_000_000_000_000,
+ token_contract_address_hash: ^token_1_contract_address_hash,
+ address_hash: ^address_1_hash_string,
+ block_number: 1_000,
+ value_fetched_at: _
+ },
+ %{
+ value: 3_000_000_000_000_000_000_000_000_000,
+ token_contract_address_hash: ^token_2_contract_address_hash,
+ address_hash: ^address_2_hash_string,
+ block_number: 1_000,
+ value_fetched_at: _
+ },
+ %{
+ value: 1,
+ token_contract_address_hash: ^token_3_contract_address_hash,
+ address_hash: ^address_2_hash_string,
+ block_number: 1_000,
+ value_fetched_at: _
+ },
+ %{
+ value: 6_000_000_000_000_000_000_000_000_000,
+ token_contract_address_hash: ^token_2_contract_address_hash,
+ address_hash: ^token_2_contract_address_hash,
+ block_number: 1_000,
+ value_fetched_at: _
+ },
+ %{
+ value: 5_000_000_000_000_000_000_000_000_000,
+ token_contract_address_hash: ^token_2_contract_address_hash,
+ address_hash: ^address_3_hash_string,
+ block_number: 1_000,
+ value_fetched_at: _
+ },
+ %{
+ value: 6_000_000_000_000_000_000_000_000_000,
+ token_contract_address_hash: ^token_2_contract_address_hash,
+ address_hash: ^token_2_contract_address_hash,
+ block_number: 1_000,
+ value_fetched_at: _
+ },
+ %{
+ value: 2,
+ token_contract_address_hash: ^token_4_contract_address_hash,
+ address_hash: ^address_2_hash_string,
+ block_number: 1_000,
+ value_fetched_at: _
+ }
+ ] = result
+ end
+
test "ignores calls that gave errors to try fetch they again later" do
address = insert(:address, hash: "0x7113ffcb9c18a97da1b9cfc43e6cb44ed9165509")
token = insert(:token, contract_address: build(:contract_address))
@@ -54,7 +228,9 @@ defmodule Indexer.TokenBalancesTest do
address_hash: to_string(address.hash),
block_number: 1_000,
token_contract_address_hash: to_string(token.contract_address_hash),
- retries_count: 1
+ retries_count: 1,
+ token_id: 11,
+ token_type: "ERC-20"
}
]
@@ -128,12 +304,14 @@ defmodule Indexer.TokenBalancesTest do
token_balance_a = %{
token_contract_address_hash: Hash.to_string(token.contract_address_hash),
+ token_id: nil,
address_hash: address_hash_string,
block_number: 1_000
}
token_balance_b = %{
token_contract_address_hash: Hash.to_string(token.contract_address_hash),
+ token_id: nil,
address_hash: address_hash_string,
block_number: 1_001
}
@@ -162,6 +340,129 @@ defmodule Indexer.TokenBalancesTest do
)
end
+ defp get_erc1155_balance_from_blockchain() do
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn requests, _options ->
+ {:ok,
+ requests
+ |> Enum.map(fn
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{
+ data:
+ "0x00fdd58e000000000000000000000000609991ca0ae39bc4eaf2669976237296d40c2f310000000000000000000000000000000000000000000000000000000000000005",
+ to: "0xf7f79032fd395978acb7069c74d21e5a53206559"
+ },
+ _
+ ]
+ } ->
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x0000000000000000000000000000000000000000000000000000000000000002"
+ }
+
+ req ->
+ IO.inspect("Gimme req")
+ IO.inspect(req)
+ end)
+ |> Enum.shuffle()}
+ end
+ )
+ end
+
+ defp get_multiple_balances_from_blockchain() do
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn requests, _options ->
+ {:ok,
+ requests
+ |> Enum.map(fn
+ %{id: id, method: "eth_call", params: [%{data: _, to: "0x57e93bb58268de818b42e3795c97bad58afcd3fe"}, _]} ->
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000"
+ }
+
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{
+ data: "0x70a08231000000000000000000000000609991ca0ae39bc4eaf2669976237296d40c2f31",
+ to: "0xe0d0b1dbbcf3dd5cac67edaf9243863fd70745da"
+ },
+ _
+ ]
+ } ->
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000000000000000000009b18ab5df7180b6b8000000"
+ }
+
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{
+ data: "0x70a08231000000000000000000000000609991ca0ae39bc4eaf2669976237296d40c2f31",
+ to: "0x22c1f6050e56d2876009903609a2cc3fef83b415"
+ },
+ _
+ ]
+ } ->
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x0000000000000000000000000000000000000000000000000000000000000001"
+ }
+
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{
+ data: "0x70a08231000000000000000000000000f712a82dd8e2ac923299193e9d6daeda2d5a32fd",
+ to: "0xe0d0b1dbbcf3dd5cac67edaf9243863fd70745da"
+ },
+ _
+ ]
+ } ->
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x00000000000000000000000000000000000000001027e72f1f12813088000000"
+ }
+
+ %{
+ id: id,
+ method: "eth_call",
+ params: [
+ %{
+ data: "0x70a08231000000000000000000000000e0d0b1dbbcf3dd5cac67edaf9243863fd70745da",
+ to: "0xe0d0b1dbbcf3dd5cac67edaf9243863fd70745da"
+ },
+ _
+ ]
+ } ->
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x00000000000000000000000000000000000000001363156bbee3016d70000000"
+ }
+ end)
+ |> Enum.shuffle()}
+ end
+ )
+ end
+
defp get_balance_from_blockchain_with_error() do
expect(
EthereumJSONRPC.Mox,
diff --git a/apps/indexer/test/indexer/transform/address_token_balances_test.exs b/apps/indexer/test/indexer/transform/address_token_balances_test.exs
index 1e22696557..82009ffab2 100644
--- a/apps/indexer/test/indexer/transform/address_token_balances_test.exs
+++ b/apps/indexer/test/indexer/transform/address_token_balances_test.exs
@@ -24,7 +24,9 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do
block_number: block_number,
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
- token_contract_address_hash: token_contract_address_hash
+ token_contract_address_hash: token_contract_address_hash,
+ token_id: nil,
+ token_type: "ERC-20"
}
params_set = AddressTokenBalances.params_set(%{token_transfers_params: [token_transfer_params]})
@@ -46,7 +48,8 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
token_contract_address_hash: token_contract_address_hash,
- token_type: "ERC-721"
+ token_type: "ERC-721",
+ token_id: nil
}
params_set = AddressTokenBalances.params_set(%{token_transfers_params: [token_transfer_params]})
@@ -56,7 +59,9 @@ defmodule Indexer.Transform.AddressTokenBalancesTest do
%{
address_hash: "0x5b8410f67eb8040bb1cd1e8a4ff9d5f6ce678a15",
block_number: 1,
- token_contract_address_hash: "0xe18035bf8712672935fdb4e5e431b1a0183d2dfc"
+ token_contract_address_hash: "0xe18035bf8712672935fdb4e5e431b1a0183d2dfc",
+ token_id: nil,
+ token_type: "ERC-721"
}
])
end
diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs
index b38c1d4088..dbea962c32 100644
--- a/apps/indexer/test/indexer/transform/token_transfers_test.exs
+++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs
@@ -74,6 +74,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
block_hash: log_3.block_hash
},
%{
+ token_id: nil,
amount: Decimal.new(17_000_000_000_000_000_000),
block_number: log_1.block_number,
log_index: log_1.index,
@@ -131,6 +132,83 @@ defmodule Indexer.Transform.TokenTransfersTest do
assert TokenTransfers.parse([log]) == expected
end
+ test "parses erc1155 token transfer" do
+ log = %{
+ address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
+ block_number: 8_683_457,
+ data:
+ "0x1000000000000c520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
+ first_topic: "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62",
+ secon_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd",
+ third_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd",
+ fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd",
+ index: 2,
+ transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
+ type: "mined"
+ }
+
+ assert TokenTransfers.parse([log]) == %{
+ token_transfers: [
+ %{
+ amount: 1,
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
+ block_number: 8_683_457,
+ from_address_hash: "0x9c978f4cfa1fe13406bcc05baf26a35716f881dd",
+ log_index: 2,
+ to_address_hash: "0x9c978f4cfa1fe13406bcc05baf26a35716f881dd",
+ token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
+ token_id:
+ 7_237_005_577_332_282_011_952_059_972_634_123_378_909_214_838_582_411_639_295_170_840_059_424_276_480,
+ token_type: "ERC-1155",
+ transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8"
+ }
+ ],
+ tokens: [
+ %{
+ contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
+ type: "ERC-1155"
+ }
+ ]
+ }
+ end
+
+ test "parses erc1155 batch token transfer" do
+ log = %{
+ address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
+ block_number: 8_683_457,
+ data:
+ "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001388",
+ first_topic: "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb",
+ secon_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3",
+ third_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3",
+ fourth_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3",
+ index: 2,
+ transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
+ type: "mined"
+ }
+
+ assert TokenTransfers.parse([log]) == %{
+ token_transfers: [
+ %{
+ block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
+ block_number: 8_683_457,
+ from_address_hash: "0x6c943470780461b00783ad530a53913bd2c104d3",
+ log_index: 2,
+ to_address_hash: "0x6c943470780461b00783ad530a53913bd2c104d3",
+ token_contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
+ token_id: nil,
+ token_ids: [680_564_733_841_876_926_926_749_214_863_536_422_912],
+ token_type: "ERC-1155",
+ transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
+ amounts: [5000]
+ }
+ ],
+ tokens: [%{contract_address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb", type: "ERC-1155"}]
+ }
+ end
+
test "logs error with unrecognized token transfer format" do
log = %{
address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",