Merge pull request #496 from poanetwork/ln-tokens-transferred-on-transaction-details
Show Token Transfers on Transaction Details pagepull/554/head
commit
96f9f20ae6
@ -0,0 +1,63 @@ |
|||||||
|
defmodule BlockScoutWeb.TransactionTokenTransferController do |
||||||
|
use BlockScoutWeb, :controller |
||||||
|
|
||||||
|
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] |
||||||
|
|
||||||
|
alias Explorer.{Chain, Market} |
||||||
|
alias Explorer.ExchangeRates.Token |
||||||
|
|
||||||
|
def index(conn, %{"transaction_id" => hash_string} = params) do |
||||||
|
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), |
||||||
|
{:ok, transaction} <- |
||||||
|
Chain.hash_to_transaction( |
||||||
|
hash, |
||||||
|
necessity_by_association: %{ |
||||||
|
block: :optional, |
||||||
|
from_address: :optional, |
||||||
|
to_address: :optional, |
||||||
|
token_transfers: :optional |
||||||
|
} |
||||||
|
) do |
||||||
|
full_options = |
||||||
|
Keyword.merge( |
||||||
|
[ |
||||||
|
necessity_by_association: %{ |
||||||
|
from_address: :required, |
||||||
|
to_address: :required, |
||||||
|
token: :required |
||||||
|
} |
||||||
|
], |
||||||
|
paging_options(params) |
||||||
|
) |
||||||
|
|
||||||
|
token_transfers_plus_one = Chain.transaction_to_token_transfers(transaction, full_options) |
||||||
|
|
||||||
|
{token_transfers, next_page} = split_list_by_page(token_transfers_plus_one) |
||||||
|
|
||||||
|
max_block_number = max_block_number() |
||||||
|
|
||||||
|
render( |
||||||
|
conn, |
||||||
|
"index.html", |
||||||
|
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), |
||||||
|
max_block_number: max_block_number, |
||||||
|
next_page_params: next_page_params(next_page, token_transfers, params), |
||||||
|
token_transfers: token_transfers, |
||||||
|
transaction: transaction |
||||||
|
) |
||||||
|
else |
||||||
|
:error -> |
||||||
|
not_found(conn) |
||||||
|
|
||||||
|
{:error, :not_found} -> |
||||||
|
not_found(conn) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp max_block_number do |
||||||
|
case Chain.max_block_number() do |
||||||
|
{:ok, number} -> number |
||||||
|
{:error, :not_found} -> 0 |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,21 @@ |
|||||||
|
<div class="tile tile-type-token fade-in"> |
||||||
|
<div class="row justify-content-end"> |
||||||
|
<div class="col-md-3 col-lg-2 d-flex align-items-center justify-content-start justify-content-lg-center tile-label"> |
||||||
|
<%= gettext("Token Transfer") %> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="col-md-9 col-lg-10 d-flex flex-column text-nowrap"> |
||||||
|
<%= render BlockScoutWeb.TransactionView, "_link.html", locale: @locale, transaction_hash: @token_transfer.transaction_hash %> |
||||||
|
<span class="text-nowrap"> |
||||||
|
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @token_transfer.from_address_hash, contract: BlockScoutWeb.AddressView.contract?(@token_transfer.from_address), locale: @locale %> |
||||||
|
→ |
||||||
|
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @token_transfer.to_address_hash, contract: BlockScoutWeb.AddressView.contract?(@token_transfer.to_address), locale: @locale %> |
||||||
|
</span> |
||||||
|
|
||||||
|
<span class="tile-title text-truncate"> |
||||||
|
<%= token_transfer_amount(@token_transfer) %> |
||||||
|
<%= link(token_symbol(@token_transfer.token), to: token_path(@conn, :show, @locale, @token_transfer.token.contract_address_hash)) %> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,83 @@ |
|||||||
|
<section class="container"> |
||||||
|
<%= render BlockScoutWeb.TransactionView, "overview.html", assigns %> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<div class="card-header"> |
||||||
|
|
||||||
|
<!-- DESKTOP TAB NAV --> |
||||||
|
<ul class="nav nav-tabs card-header-tabs d-none d-md-inline-flex"> |
||||||
|
<li class="nav-item"> |
||||||
|
<%= link( |
||||||
|
gettext("Token Transfers"), |
||||||
|
class: "nav-link active", |
||||||
|
to: transaction_token_transfer_path(@conn, :index, @conn.assigns.locale, @transaction) |
||||||
|
) %> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<%= link( |
||||||
|
gettext("Internal Transactions"), |
||||||
|
class: "nav-link", |
||||||
|
to: transaction_internal_transaction_path(@conn, :index, @conn.assigns.locale, @transaction) |
||||||
|
) %> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<%= link( |
||||||
|
gettext("Logs"), |
||||||
|
class: "nav-link", |
||||||
|
to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction) |
||||||
|
) %> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<!-- MOBILE DROPDOWN NAV --> |
||||||
|
<ul class="nav nav-tabs card-header-tabs d-md-none"> |
||||||
|
<li class="nav-item dropdown flex-fill text-center"> |
||||||
|
<a class="nav-link active dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false"><%= gettext("Token Transfers") %></a> |
||||||
|
<div class="dropdown-menu"> |
||||||
|
<%= link( |
||||||
|
gettext("Token Transfers"), |
||||||
|
class: "dropdown-item", |
||||||
|
to: transaction_token_transfer_path(@conn, :index, @conn.assigns.locale, @transaction) |
||||||
|
) %> |
||||||
|
<%= link( |
||||||
|
gettext("Internal Transactions"), |
||||||
|
class: "dropdown-item", |
||||||
|
to: transaction_internal_transaction_path(@conn, :index, @conn.assigns.locale, @transaction) |
||||||
|
) %> |
||||||
|
<%= link( |
||||||
|
gettext("Logs"), |
||||||
|
class: "dropdown-item", |
||||||
|
to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction) |
||||||
|
) %> |
||||||
|
</div> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="card-body"> |
||||||
|
<h2 class="card-title"><%= gettext "Token Transfers" %></h2> |
||||||
|
<%= if Enum.any?(@token_transfers) do %> |
||||||
|
<%= for token_transfer <- @token_transfers do %> |
||||||
|
<%= render "_token_transfer.html", locale: @locale, token_transfer: token_transfer, conn: @conn %> |
||||||
|
<% end %> |
||||||
|
<% else %> |
||||||
|
<div class="tile tile-muted text-center"> |
||||||
|
<span><%= gettext "There are no token transfers for this transaction." %></span> |
||||||
|
</div> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<%= if @next_page_params do %> |
||||||
|
<%= link( |
||||||
|
gettext("Older"), |
||||||
|
class: "button button--secondary button--sm u-float-left mt-3", |
||||||
|
to: transaction_token_transfer_path( |
||||||
|
@conn, |
||||||
|
:index, |
||||||
|
@conn.assigns.locale, |
||||||
|
@transaction, |
||||||
|
@next_page_params |
||||||
|
) |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
</section> |
@ -0,0 +1,3 @@ |
|||||||
|
defmodule BlockScoutWeb.TransactionTokenTransferView do |
||||||
|
use BlockScoutWeb, :view |
||||||
|
end |
@ -0,0 +1,140 @@ |
|||||||
|
defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
import BlockScoutWeb.Router.Helpers, only: [transaction_token_transfer_path: 4] |
||||||
|
|
||||||
|
alias Explorer.ExchangeRates.Token |
||||||
|
|
||||||
|
describe "GET index/3" do |
||||||
|
test "load token transfers", %{conn: conn} do |
||||||
|
transaction = insert(:transaction) |
||||||
|
token_transfer = insert(:token_transfer, transaction: transaction) |
||||||
|
|
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash)) |
||||||
|
|
||||||
|
assert List.first(conn.assigns.transaction.token_transfers).id == token_transfer.id |
||||||
|
end |
||||||
|
|
||||||
|
test "with missing transaction", %{conn: conn} do |
||||||
|
hash = transaction_hash() |
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, hash)) |
||||||
|
|
||||||
|
assert html_response(conn, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "with invalid transaction hash", %{conn: conn} do |
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, "nope")) |
||||||
|
|
||||||
|
assert html_response(conn, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "includes transaction data", %{conn: conn} do |
||||||
|
block = insert(:block, %{number: 777}) |
||||||
|
|
||||||
|
transaction = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block(block) |
||||||
|
|
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash)) |
||||||
|
|
||||||
|
assert html_response(conn, 200) |
||||||
|
assert conn.assigns.transaction.hash == transaction.hash |
||||||
|
end |
||||||
|
|
||||||
|
test "includes token transfers for the transaction", %{conn: conn} do |
||||||
|
transaction = insert(:transaction) |
||||||
|
|
||||||
|
expected_token_transfer = insert(:token_transfer, transaction: transaction) |
||||||
|
|
||||||
|
insert(:token_transfer, transaction: transaction) |
||||||
|
|
||||||
|
path = transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash) |
||||||
|
|
||||||
|
conn = get(conn, path) |
||||||
|
|
||||||
|
actual_token_transfer_ids = |
||||||
|
conn.assigns.token_transfers |
||||||
|
|> Enum.map(fn it -> it.id end) |
||||||
|
|
||||||
|
assert html_response(conn, 200) |
||||||
|
|
||||||
|
assert Enum.member?(actual_token_transfer_ids, expected_token_transfer.id) |
||||||
|
end |
||||||
|
|
||||||
|
test "includes USD exchange rate value for address in assigns", %{conn: conn} do |
||||||
|
transaction = insert(:transaction) |
||||||
|
|
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash)) |
||||||
|
|
||||||
|
assert %Token{} = conn.assigns.exchange_rate |
||||||
|
end |
||||||
|
|
||||||
|
test "returns next page of results based on last seen token transfer", %{conn: conn} do |
||||||
|
transaction = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
{:ok, first_transfer_time} = NaiveDateTime.new(2000, 1, 1, 0, 0, 5) |
||||||
|
{:ok, remaining_transfers_time} = NaiveDateTime.new(1999, 1, 1, 0, 0, 0) |
||||||
|
insert(:token_transfer, transaction: transaction, inserted_at: first_transfer_time) |
||||||
|
|
||||||
|
1..5 |
||||||
|
|> Enum.each(fn log_index -> |
||||||
|
insert(:token_transfer, transaction: transaction, inserted_at: remaining_transfers_time, log_index: log_index) |
||||||
|
end) |
||||||
|
|
||||||
|
conn = |
||||||
|
get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash), %{ |
||||||
|
"inserted_at" => first_transfer_time |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601() |
||||||
|
}) |
||||||
|
|
||||||
|
actual_times = |
||||||
|
conn.assigns.token_transfers |
||||||
|
|> Enum.map(& &1.inserted_at) |
||||||
|
|
||||||
|
refute Enum.any?(actual_times, fn time -> first_transfer_time == time end) |
||||||
|
end |
||||||
|
|
||||||
|
test "next_page_params exist if not on last page", %{conn: conn} do |
||||||
|
transaction = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
1..51 |
||||||
|
|> Enum.map(fn log_index -> |
||||||
|
insert( |
||||||
|
:token_transfer, |
||||||
|
transaction: transaction, |
||||||
|
log_index: log_index |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash)) |
||||||
|
|
||||||
|
assert Enum.any?(conn.assigns.next_page_params) |
||||||
|
end |
||||||
|
|
||||||
|
test "next_page_params are empty if on last page", %{conn: conn} do |
||||||
|
transaction = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
1..2 |
||||||
|
|> Enum.map(fn log_index -> |
||||||
|
insert( |
||||||
|
:token_transfer, |
||||||
|
transaction: transaction, |
||||||
|
log_index: log_index |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, :en, transaction.hash)) |
||||||
|
|
||||||
|
assert is_nil(conn.assigns.next_page_params) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue