commit
eaadac2fcc
@ -0,0 +1,77 @@ |
||||
defmodule BlockScoutWeb.Tokens.Instance.HolderController do |
||||
use BlockScoutWeb, :controller |
||||
|
||||
alias BlockScoutWeb.Tokens.HolderView |
||||
alias Explorer.{Chain, Market} |
||||
alias Explorer.Chain.Address |
||||
alias Phoenix.View |
||||
|
||||
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] |
||||
|
||||
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id, "type" => "JSON"} = params) do |
||||
with {:ok, address_hash} <- Chain.string_to_address_hash(token_address_hash), |
||||
{:ok, token} <- Chain.token_from_address_hash(address_hash), |
||||
token_holders <- |
||||
Chain.fetch_token_holders_from_token_hash_and_token_id(address_hash, token_id, paging_options(params)) do |
||||
{token_holders_paginated, next_page} = split_list_by_page(token_holders) |
||||
|
||||
next_page_path = |
||||
case next_page_params(next_page, token_holders_paginated, params) do |
||||
nil -> |
||||
nil |
||||
|
||||
next_page_params -> |
||||
token_instance_holder_path( |
||||
conn, |
||||
:index, |
||||
Address.checksum(token.contract_address_hash), |
||||
token_id, |
||||
Map.delete(next_page_params, "type") |
||||
) |
||||
end |
||||
|
||||
holders_json = |
||||
token_holders_paginated |
||||
|> Enum.sort_by(& &1.value, &>=/2) |
||||
|> Enum.map(fn current_token_balance -> |
||||
View.render_to_string( |
||||
HolderView, |
||||
"_token_balances.html", |
||||
address_hash: address_hash, |
||||
token_balance: current_token_balance, |
||||
token: token |
||||
) |
||||
end) |
||||
|
||||
json(conn, %{items: holders_json, next_page_path: next_page_path}) |
||||
else |
||||
_ -> |
||||
not_found(conn) |
||||
end |
||||
end |
||||
|
||||
def index(conn, %{"token_id" => token_address_hash, "instance_id" => token_id}) do |
||||
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] |
||||
|
||||
with {:ok, hash} <- Chain.string_to_address_hash(token_address_hash), |
||||
{:ok, token} <- Chain.token_from_address_hash(hash, options), |
||||
{:ok, token_transfer} <- |
||||
Chain.erc721_token_instance_from_token_id_and_token_address(token_id, hash) do |
||||
render( |
||||
conn, |
||||
"index.html", |
||||
token_instance: token_transfer, |
||||
current_path: current_path(conn), |
||||
token: Market.add_price(token), |
||||
total_token_transfers: Chain.count_token_transfers_from_token_hash_and_token_id(hash, token_id) |
||||
) |
||||
else |
||||
_ -> |
||||
not_found(conn) |
||||
end |
||||
end |
||||
|
||||
def index(conn, _) do |
||||
not_found(conn) |
||||
end |
||||
end |
@ -0,0 +1,12 @@ |
||||
<%= case @type do %> |
||||
<% :token_burning -> %> |
||||
<%= gettext("Token Burning") %> |
||||
<% :token_minting -> %> |
||||
<%= gettext("Token Minting") %> |
||||
<% :token_spawning -> %> |
||||
<%= gettext("Token Creation") %> |
||||
<% :token_transfer -> %> |
||||
<%= gettext("Token Transfer") %> |
||||
<% _ -> %> |
||||
<%= gettext("Token Transfer") %> |
||||
<% end %> |
@ -0,0 +1,41 @@ |
||||
<section class="container"> |
||||
<%= render( |
||||
OverviewView, |
||||
"_details.html", |
||||
token: @token, |
||||
total_token_transfers: @total_token_transfers, |
||||
token_id: @token_instance.token_id, |
||||
token_instance: @token_instance, |
||||
conn: @conn |
||||
) %> |
||||
|
||||
<section> |
||||
<div class="card"> |
||||
<%= render OverviewView, "_tabs.html", assigns %> |
||||
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>"> |
||||
<h2 class="card-title list-title-description"><%= gettext "Token Holders" %></h2> |
||||
|
||||
<div class="list-top-pagination-container-wrapper"> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> |
||||
</div> |
||||
|
||||
<button data-error-message class="alert alert-danger col-12 text-left" style="display: none;"> |
||||
<span href="#" class="alert-link"><%= gettext("Something went wrong, click to reload.") %></span> |
||||
</button> |
||||
|
||||
<div data-empty-response-message class="tile tile-muted text-center" style="display: none;"> |
||||
<span data-selector="empty-transactions-list"> |
||||
<%= gettext "There are no transfers for this Token." %> |
||||
</span> |
||||
</div> |
||||
|
||||
<div data-items> |
||||
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> |
||||
</div> |
||||
|
||||
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> |
||||
|
||||
</div> |
||||
</div> |
||||
</section> |
||||
</section> |
@ -0,0 +1 @@ |
||||
<%= "[" %><%= link(short_token_id(@token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@token_id)), "data-test": "token_link") %><%= "]" %> |
@ -0,0 +1 @@ |
||||
<%= link(token_symbol(@transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash), "data-test": "token_link") %> |
@ -1,8 +1,23 @@ |
||||
<%= case token_transfer_amount(@transfer) do %> |
||||
<% {:ok, :erc721_instance} -> %> |
||||
<%= "TokenID ["%><%= link(short_token_id(@transfer.token_id, 30), to: token_instance_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash, to_string(@transfer.token_id)), "data-test": "token_link") %><%= "]" %> |
||||
<%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> |
||||
<% {:ok, :erc1155_instance, value} -> %> |
||||
<% transfer_type = Chain.get_token_transfer_type(@transfer) %> |
||||
<%= if transfer_type == :token_spawning do %> |
||||
<%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> |
||||
<% else %> |
||||
<%= "#{value} " %> |
||||
<%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: @transfer.token_id %> |
||||
<% end %> |
||||
<% {:ok, :erc1155_instance, values, token_ids, _decimals} -> %> |
||||
<% values_ids = Enum.zip(values, token_ids) %> |
||||
<%= for {value, token_id} <- values_ids do %> |
||||
<div> |
||||
<%= "#{value} "%> |
||||
<%= render BlockScoutWeb.TransactionView, "_transfer_token_with_id.html", transfer: @transfer, token_id: token_id %> |
||||
</div> |
||||
<% end %> |
||||
<% {:ok, value} -> %> |
||||
<%= value %> |
||||
<% end %> |
||||
<%= " "%> |
||||
<%= link(token_symbol(@transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, @transfer.token.contract_address_hash), "data-test": "token_link") %> |
||||
<%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %> |
||||
<% end %> |
@ -0,0 +1,2 @@ |
||||
<%= "TokenID " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_instance.html", transfer: @transfer, token_id: @token_id %> |
||||
<%= " " %><%= render BlockScoutWeb.TransactionView, "_link_to_token_symbol.html", transfer: @transfer %> |
@ -0,0 +1,5 @@ |
||||
defmodule BlockScoutWeb.Tokens.Instance.HolderView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.Tokens.Instance.OverviewView |
||||
end |
@ -1,3 +1,5 @@ |
||||
defmodule BlockScoutWeb.TransactionTokenTransferView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias Explorer.Chain |
||||
end |
||||
|
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 |
Loading…
Reference in new issue