Merge branch 'master' into 742-redesign-top-menu

pull/749/head
Andrew Cravenho 6 years ago committed by GitHub
commit b2baa64010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 47
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  2. 19
      apps/block_scout_web/lib/block_scout_web/router.ex
  3. 6
      apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
  4. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  5. 148
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  6. 3
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  7. 24
      apps/block_scout_web/lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex
  8. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/token/show.html.eex
  9. 5
      apps/block_scout_web/lib/block_scout_web/views/address_token_transfer_view.ex
  10. 55
      apps/block_scout_web/priv/gettext/default.pot
  11. 55
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  12. 114
      apps/block_scout_web/test/block_scout_web/controllers/address_token_transfer_controller_test.exs
  13. 10
      apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex
  14. 38
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  15. 21
      apps/explorer/lib/explorer/chain.ex
  16. 29
      apps/explorer/lib/explorer/chain/transaction.ex
  17. 140
      apps/explorer/test/explorer/chain/transaction_test.exs
  18. 76
      apps/explorer/test/explorer/chain_test.exs

@ -0,0 +1,47 @@
defmodule BlockScoutWeb.AddressTokenTransferController do
use BlockScoutWeb, :controller
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.Chain,
only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
def index(
conn,
%{"address_id" => address_hash_string, "address_token_id" => token_hash_string} = params
) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash),
{:ok, token} <- Chain.token_from_address_hash(token_hash) do
transactions =
Chain.address_to_transactions_with_token_tranfers(
address_hash,
token_hash,
paging_options(params)
)
{transactions_paginated, next_page} = split_list_by_page(transactions)
render(
conn,
"index.html",
address: address,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
next_page_params: next_page_params(next_page, transactions_paginated, params),
token: token,
transaction_count: transaction_count(address),
transactions: transactions_paginated
)
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -58,7 +58,10 @@ defmodule BlockScoutWeb.Router do
resources("/logs", TransactionLogController, only: [:index], as: :log)
resources("/token_transfers", TransactionTokenTransferController, only: [:index], as: :token_transfer)
resources("/token_transfers", TransactionTokenTransferController,
only: [:index],
as: :token_transfer
)
end
resources("/accounts", AddressController, only: [:index])
@ -94,12 +97,14 @@ defmodule BlockScoutWeb.Router do
as: :read_contract
)
resources(
"/tokens",
AddressTokenController,
only: [:index],
as: :token
)
resources("/tokens", AddressTokenController, only: [:index], as: :token) do
resources(
"/token_transfers",
AddressTokenTransferController,
only: [:index],
as: :transfers
)
end
resources(
"/token_balances",

@ -1,7 +1,11 @@
<div class="tile tile-type-token">
<div class="row justify-content align-items-center">
<div class="col-md-7 d-flex flex-column mt-3 mt-md-0">
<%= link(to: token_path(@conn, :show, @token.contract_address_hash), class: "tile-title-lg") do %>
<%= link(
to: address_token_transfers_path(@conn, :index, @address.hash, @token.contract_address_hash),
class: "tile-title-lg",
"data-test": "token_transfers_#{@token.contract_address_hash}"
) do %>
<%= token_name(@token) %>
<% end %>
<span><%= @token.type %> - <%= number_of_transfers(@token) %></span>

@ -109,7 +109,7 @@
<h2 class="card-title"><%= gettext "Tokens" %></h2>
<%= if Enum.any?(@tokens) do %>
<%= for token <- @tokens do %>
<%= render "_tokens.html", conn: @conn, token: token %>
<%= render "_tokens.html", conn: @conn, token: token, address: @address %>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
@ -121,7 +121,7 @@
<%= if @next_page_params do %>
<%= link(
gettext("Next"),
class: "button button-secondary button-sm float-right",
class: "button button-secondary button-sm float-right mt-3",
to: address_token_path(
@conn,
:index,

@ -0,0 +1,148 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<section>
<div class="card">
<div class="card-header">
<!-- DESKTOP TAB NAV -->
<ul class="nav nav-tabs card-header-tabs d-none d-lg-inline-flex">
<li class="nav-item">
<%= link(
gettext("Transactions"),
class: "nav-link",
to: address_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item">
<%= link(
gettext("Tokens"),
class: "nav-link active",
to: address_token_path(@conn, :index, @address.hash)
) %>
</li>
<li class="nav-item">
<%= link(
gettext("Internal Transactions"),
class: "nav-link",
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
</li>
<%= if AddressView.contract?(@address) do %>
<li class="nav-item">
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "nav-link") do %>
<%= gettext("Code") %>
<%= if AddressView.smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
</li>
<% end %>
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "nav-link")%>
</li>
<% end %>
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %>
<li class="nav-item">
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "nav-link")%>
</li>
<% end %>
</ul>
<!-- MOBILE DROPDOWN NAV -->
<ul class="nav nav-tabs card-header-tabs d-lg-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("Tokens") %>
</a>
<div class="dropdown-menu">
<%= link(
gettext("Transactions"),
class: "dropdown-item",
to: address_transaction_path(@conn, :index, @address.hash)
) %>
<%= link(
gettext("Tokens"),
class: "dropdown-item active",
to: address_token_path(@conn, :index, @address.hash)
) %>
<%= link(
gettext("Internal Transactions"),
class: "dropdown-item",
"data-test": "internal_transactions_tab_link",
to: address_internal_transaction_path(@conn, :index, @address.hash)
) %>
<%= link(
to: address_contract_path(@conn, :index, @address.hash),
class: "dropdown-item") do %>
<%= gettext("Code") %>
<%= if AddressView.smart_contract_verified?(@address) do %>
<i class="far fa-check-circle"></i>
<% end %>
<% end %>
<%= if AddressView.smart_contract_with_read_only_functions?(@address) do %>
<%= link(
gettext("Read Contract"),
to: address_read_contract_path(@conn, :index, @address.hash),
class: "dropdown-item"
)%>
<% end %>
</div>
</li>
</ul>
</div>
<div class="card-body">
<h2 class="card-title">
<span class="text-muted"><%= gettext "Tokens" %></span> / <%= token_name(@token) %>
</h2>
<%= if Enum.any?(@transactions) do %>
<span data-selector="transactions-list">
<%= for transaction <- @transactions do %>
<%= render(
BlockScoutWeb.TransactionView,
"_tile.html",
transaction: transaction,
current_address: @address
) %>
<% end %>
</span>
<% else %>
<div class="tile tile-muted text-center">
<span><%= gettext "There are no token transfers for this address." %></span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Next"),
class: "button button-secondary button-sm float-right mt-3",
to: address_token_transfers_path(
@conn,
:index,
@address.hash,
@token.contract_address_hash,
@next_page_params
)
) %>
<% end %>
</div>
</div>
</section>
</section>

@ -19,7 +19,8 @@
<%= link(
gettext("Tokens"),
class: "nav-link",
to: address_token_path(@conn, :index, @address.hash)
to: address_token_path(@conn, :index, @address.hash),
"data-test": "tokens_tab_link"
) %>
</li>
<li class="nav-item">

@ -9,17 +9,25 @@
<%= render BlockScoutWeb.TransactionView, "_link.html", transaction_hash: @transfer.transaction_hash %>
</p>
<span>
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @transfer.from_address,
contract: BlockScoutWeb.AddressView.contract?(@transfer.from_address) %>
<%= link to: address_token_transfers_path(@conn, :index, @transfer.from_address, @token.contract_address_hash), "data-test": "address_hash_link" do %>
<%= render(
BlockScoutWeb.AddressView,
"_responsive_hash.html",
address: @transfer.from_address,
contract: BlockScoutWeb.AddressView.contract?(@transfer.from_address)
) %>
<% end %>
&rarr;
<%= render BlockScoutWeb.AddressView,
"_link.html",
address: @transfer.to_address,
contract: BlockScoutWeb.AddressView.contract?(@transfer.to_address) %>
<%= link to: address_token_transfers_path(@conn, :index, @transfer.to_address, @token.contract_address_hash), "data-test": "address_hash_link" do %>
<%= render(
BlockScoutWeb.AddressView,
"_responsive_hash.html",
address: @transfer.to_address,
contract: BlockScoutWeb.AddressView.contract?(@transfer.to_address)
) %>
<% end %>
</span>
<span>

@ -71,7 +71,7 @@
<%= if Enum.any?(@transfers) do %>
<%= for transfer <- @transfers do %>
<%= render("_token_transfer.html", token: @token, transfer: transfer) %>
<%= render("_token_transfer.html", conn: @conn, token: @token, transfer: transfer) %>
<% end %>
<% else %>
<div class="tile tile-muted text-center">

@ -0,0 +1,5 @@
defmodule BlockScoutWeb.AddressTokenTransferView do
use BlockScoutWeb, :view
alias BlockScoutWeb.AddressView
end

@ -43,10 +43,12 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:59
#: lib/block_scout_web/templates/address_token/index.html.eex:11
#: lib/block_scout_web/templates/address_token/index.html.eex:73
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:74
#: lib/block_scout_web/templates/address_transaction/index.html.eex:13
#: lib/block_scout_web/templates/address_transaction/index.html.eex:59
#: lib/block_scout_web/templates/address_transaction/index.html.eex:62
#: lib/block_scout_web/templates/address_transaction/index.html.eex:147
#: lib/block_scout_web/templates/address_transaction/index.html.eex:60
#: lib/block_scout_web/templates/address_transaction/index.html.eex:63
#: lib/block_scout_web/templates/address_transaction/index.html.eex:148
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26
@ -144,7 +146,7 @@ msgid "Address"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:131
#: lib/block_scout_web/templates/address_transaction/index.html.eex:134
#: lib/block_scout_web/templates/address_transaction/index.html.eex:135
#: lib/block_scout_web/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:10
msgid "From"
@ -160,7 +162,7 @@ msgid "Success"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:120
#: lib/block_scout_web/templates/address_transaction/index.html.eex:123
#: lib/block_scout_web/templates/address_transaction/index.html.eex:124
#: lib/block_scout_web/views/address_internal_transaction_view.ex:9
#: lib/block_scout_web/views/address_transaction_view.ex:9
msgid "To"
@ -319,8 +321,10 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:69
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_token/index.html.eex:83
#: lib/block_scout_web/templates/address_transaction/index.html.eex:27
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:27
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:84
#: lib/block_scout_web/templates/address_transaction/index.html.eex:28
#: lib/block_scout_web/templates/address_transaction/index.html.eex:73
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:20
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:38
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:49
@ -367,7 +371,7 @@ msgid "Wei"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:114
#: lib/block_scout_web/templates/address_transaction/index.html.eex:117
#: lib/block_scout_web/templates/address_transaction/index.html.eex:118
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11
msgid "All"
@ -456,8 +460,12 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:69
#: lib/block_scout_web/templates/address_token/index.html.eex:78
#: lib/block_scout_web/templates/address_token/index.html.eex:109
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:70
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:79
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:112
#: lib/block_scout_web/templates/address_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:67
#: lib/block_scout_web/templates/address_transaction/index.html.eex:68
msgid "Tokens"
msgstr ""
@ -502,7 +510,7 @@ msgid "There are no Transactions"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:158
#: lib/block_scout_web/templates/address_transaction/index.html.eex:161
#: lib/block_scout_web/templates/address_transaction/index.html.eex:162
#: lib/block_scout_web/templates/block/index.html.eex:20
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:78
@ -529,8 +537,10 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:77
#: lib/block_scout_web/templates/address_token/index.html.eex:38
#: lib/block_scout_web/templates/address_token/index.html.eex:91
#: lib/block_scout_web/templates/address_transaction/index.html.eex:38
#: lib/block_scout_web/templates/address_transaction/index.html.eex:81
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:39
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:92
#: lib/block_scout_web/templates/address_transaction/index.html.eex:39
#: lib/block_scout_web/templates/address_transaction/index.html.eex:82
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:122
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
msgid "Code"
@ -607,7 +617,7 @@ msgid "Wallet addresses"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:107
#: lib/block_scout_web/templates/address_transaction/index.html.eex:108
#: lib/block_scout_web/templates/transaction/index.html.eex:53
msgid "Connection Lost, click to load newer transactions"
msgstr ""
@ -640,7 +650,7 @@ msgid "There are no internal transactions for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:155
#: lib/block_scout_web/templates/address_transaction/index.html.eex:156
#: lib/block_scout_web/templates/block_transaction/index.html.eex:44
msgid "There are no transactions for this address."
msgstr ""
@ -669,7 +679,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:21
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:36
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:44
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30
msgid "Block #%{number}"
msgstr ""
@ -702,8 +712,11 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:50
#: lib/block_scout_web/templates/address_token/index.html.eex:58
#: lib/block_scout_web/templates/address_token/index.html.eex:99
#: lib/block_scout_web/templates/address_transaction/index.html.eex:49
#: lib/block_scout_web/templates/address_transaction/index.html.eex:90
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:51
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:59
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:100
#: lib/block_scout_web/templates/address_transaction/index.html.eex:50
#: lib/block_scout_web/templates/address_transaction/index.html.eex:91
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:27
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:55
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:26
@ -783,7 +796,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:102
#: lib/block_scout_web/templates/address_transaction/index.html.eex:103
#: lib/block_scout_web/templates/chain/show.html.eex:69
#: lib/block_scout_web/templates/transaction/index.html.eex:48
msgid "More transactions have come in"
@ -1076,6 +1089,7 @@ msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:123
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:134
msgid "Next"
msgstr ""
@ -1253,3 +1267,8 @@ msgstr ""
#: lib/block_scout_web/templates/address/index.html.eex:8
msgid "total addresses with a balance"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:128
msgid "There are no token transfers for this address."
msgstr ""

@ -55,10 +55,12 @@ msgstr "BlockScout"
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:59
#: lib/block_scout_web/templates/address_token/index.html.eex:11
#: lib/block_scout_web/templates/address_token/index.html.eex:73
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:11
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:74
#: lib/block_scout_web/templates/address_transaction/index.html.eex:13
#: lib/block_scout_web/templates/address_transaction/index.html.eex:59
#: lib/block_scout_web/templates/address_transaction/index.html.eex:62
#: lib/block_scout_web/templates/address_transaction/index.html.eex:147
#: lib/block_scout_web/templates/address_transaction/index.html.eex:60
#: lib/block_scout_web/templates/address_transaction/index.html.eex:63
#: lib/block_scout_web/templates/address_transaction/index.html.eex:148
#: lib/block_scout_web/templates/block_transaction/index.html.eex:13
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/block_transaction/index.html.eex:26
@ -156,7 +158,7 @@ msgid "Address"
msgstr "Address"
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:131
#: lib/block_scout_web/templates/address_transaction/index.html.eex:134
#: lib/block_scout_web/templates/address_transaction/index.html.eex:135
#: lib/block_scout_web/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:10
msgid "From"
@ -172,7 +174,7 @@ msgid "Success"
msgstr "Success"
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:120
#: lib/block_scout_web/templates/address_transaction/index.html.eex:123
#: lib/block_scout_web/templates/address_transaction/index.html.eex:124
#: lib/block_scout_web/views/address_internal_transaction_view.ex:9
#: lib/block_scout_web/views/address_transaction_view.ex:9
msgid "To"
@ -331,8 +333,10 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:69
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_token/index.html.eex:83
#: lib/block_scout_web/templates/address_transaction/index.html.eex:27
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:27
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:84
#: lib/block_scout_web/templates/address_transaction/index.html.eex:28
#: lib/block_scout_web/templates/address_transaction/index.html.eex:73
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:20
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:38
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:49
@ -379,7 +383,7 @@ msgid "Wei"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:114
#: lib/block_scout_web/templates/address_transaction/index.html.eex:117
#: lib/block_scout_web/templates/address_transaction/index.html.eex:118
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11
msgid "All"
@ -468,8 +472,12 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:69
#: lib/block_scout_web/templates/address_token/index.html.eex:78
#: lib/block_scout_web/templates/address_token/index.html.eex:109
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:70
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:79
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:112
#: lib/block_scout_web/templates/address_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:67
#: lib/block_scout_web/templates/address_transaction/index.html.eex:68
msgid "Tokens"
msgstr ""
@ -514,7 +522,7 @@ msgid "There are no Transactions"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:158
#: lib/block_scout_web/templates/address_transaction/index.html.eex:161
#: lib/block_scout_web/templates/address_transaction/index.html.eex:162
#: lib/block_scout_web/templates/block/index.html.eex:20
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:78
@ -541,8 +549,10 @@ msgstr ""
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:77
#: lib/block_scout_web/templates/address_token/index.html.eex:38
#: lib/block_scout_web/templates/address_token/index.html.eex:91
#: lib/block_scout_web/templates/address_transaction/index.html.eex:38
#: lib/block_scout_web/templates/address_transaction/index.html.eex:81
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:39
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:92
#: lib/block_scout_web/templates/address_transaction/index.html.eex:39
#: lib/block_scout_web/templates/address_transaction/index.html.eex:82
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:122
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141
msgid "Code"
@ -619,7 +629,7 @@ msgid "Wallet addresses"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:107
#: lib/block_scout_web/templates/address_transaction/index.html.eex:108
#: lib/block_scout_web/templates/transaction/index.html.eex:53
msgid "Connection Lost, click to load newer transactions"
msgstr ""
@ -652,7 +662,7 @@ msgid "There are no internal transactions for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:155
#: lib/block_scout_web/templates/address_transaction/index.html.eex:156
#: lib/block_scout_web/templates/block_transaction/index.html.eex:44
msgid "There are no transactions for this address."
msgstr ""
@ -681,7 +691,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:21
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:36
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:44
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30
msgid "Block #%{number}"
msgstr ""
@ -714,8 +724,11 @@ msgstr ""
#: lib/block_scout_web/templates/address_token/index.html.eex:50
#: lib/block_scout_web/templates/address_token/index.html.eex:58
#: lib/block_scout_web/templates/address_token/index.html.eex:99
#: lib/block_scout_web/templates/address_transaction/index.html.eex:49
#: lib/block_scout_web/templates/address_transaction/index.html.eex:90
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:51
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:59
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:100
#: lib/block_scout_web/templates/address_transaction/index.html.eex:50
#: lib/block_scout_web/templates/address_transaction/index.html.eex:91
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:27
#: lib/block_scout_web/templates/tokens/holder/index.html.eex:55
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:26
@ -795,7 +808,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:102
#: lib/block_scout_web/templates/address_transaction/index.html.eex:103
#: lib/block_scout_web/templates/chain/show.html.eex:69
#: lib/block_scout_web/templates/transaction/index.html.eex:48
msgid "More transactions have come in"
@ -1088,6 +1101,7 @@ msgstr[1] ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:123
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:134
msgid "Next"
msgstr ""
@ -1265,3 +1279,8 @@ msgstr ""
#: lib/block_scout_web/templates/address/index.html.eex:8
msgid "total addresses with a balance"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:128
msgid "There are no token transfers for this address."
msgstr ""

@ -0,0 +1,114 @@
defmodule BlockScoutWeb.AddressTokenTransferControllerTest do
use BlockScoutWeb.ConnCase
import BlockScoutWeb.Router.Helpers, only: [address_token_transfers_path: 4]
alias Explorer.Chain.{Address, Token}
describe "GET index/2" do
test "with invalid address hash", %{conn: conn} do
token_hash = "0xc8982771dd50285389c352c175ada74d074427c7"
conn = get(conn, address_token_transfers_path(conn, :index, "invalid_address", token_hash))
assert html_response(conn, 422)
end
test "with invalid token hash", %{conn: conn} do
address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, "invalid_address"))
assert html_response(conn, 422)
end
test "with an address that doesn't exist in our database", %{conn: conn} do
address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
%Token{contract_address_hash: token_hash} = insert(:token)
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash))
assert html_response(conn, 404)
end
test "with an token that doesn't exist in our database", %{conn: conn} do
%Address{hash: address_hash} = insert(:address)
token_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash))
assert html_response(conn, 404)
end
test "without token transfers for a token", %{conn: conn} do
%Address{hash: address_hash} = insert(:address)
%Token{contract_address_hash: token_hash} = insert(:token)
conn = get(conn, address_token_transfers_path(conn, :index, address_hash, token_hash))
assert html_response(conn, 200)
assert conn.assigns.transactions == []
end
test "returns the transactions that have token transfers for the given address and token", %{conn: conn} do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
conn = get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash))
transaction_hashes = Enum.map(conn.assigns.transactions, & &1.hash)
assert html_response(conn, 200)
assert transaction_hashes == [transaction.hash]
end
test "returns next page of results based on last seen transactions", %{conn: conn} do
address = insert(:address)
token = insert(:token)
second_page_transactions =
1..50
|> Enum.map(fn index ->
block = insert(:block, number: 1000 - index)
transaction =
:transaction
|> insert()
|> with_block(block)
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
transaction
end)
|> Enum.map(& &1.hash)
transaction =
:transaction
|> insert()
|> with_block(insert(:block, number: 1002))
conn =
get(conn, address_token_transfers_path(conn, :index, address.hash, token.contract_address_hash), %{
"block_number" => Integer.to_string(transaction.block_number),
"index" => Integer.to_string(transaction.index)
})
actual_transactions = Enum.map(conn.assigns.transactions, & &1.hash)
assert second_page_transactions == actual_transactions
end
end
end

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.AddressPage do
use Wallaby.DSL
import Wallaby.Query, only: [css: 1, css: 2]
alias Explorer.Chain.{Address, InternalTransaction, Hash, Transaction}
alias Explorer.Chain.{Address, InternalTransaction, Hash, Transaction, Token}
def apply_filter(session, direction) do
session
@ -27,6 +27,14 @@ defmodule BlockScoutWeb.AddressPage do
click(session, css("[data-test='internal_transactions_tab_link']"))
end
def click_tokens(session) do
click(session, css("[data-test='tokens_tab_link']"))
end
def click_token_transfers(session, %Token{contract_address_hash: contract_address_hash}) do
click(session, css("[data-test='token_transfers_#{contract_address_hash}']"))
end
def contract_creation(%InternalTransaction{created_contract_address_hash: hash}) do
css("[data-address-hash='#{hash}']", text: to_string(hash))
end

@ -422,4 +422,42 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> assert_has(AddressPage.token_transfers(transaction, count: 3))
end
end
describe "viewing token transfers from a specific token" do
test "list token transfers related to the address", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
taft = addresses.taft
contract_address = insert(:contract_address)
token = insert(:token, contract_address: contract_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_address)
|> with_block(block)
insert(
:token_transfer,
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_address
)
insert(:token_balance, address: lincoln, token_contract_address_hash: contract_address.hash)
session
|> AddressPage.visit_page(lincoln)
|> AddressPage.click_tokens()
|> AddressPage.click_token_transfers(token)
|> assert_has(AddressPage.token_transfers(transaction, count: 1))
|> assert_has(AddressPage.token_transfer(transaction, lincoln, count: 1))
|> assert_has(AddressPage.token_transfer(transaction, taft, count: 1))
|> refute_has(AddressPage.token_transfers_expansion(transaction))
end
end
end

@ -222,6 +222,27 @@ defmodule Explorer.Chain do
|> Enum.slice(0..paging_options.page_size)
end
@doc """
Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract
address hash.
## Options
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (in the form of `%{"inserted_at" => inserted_at}`). Results will be the transactions
older than the `index` that are passed.
"""
@spec address_to_transactions_with_token_tranfers(Hash.t(), Hash.t(), [paging_options]) :: [Transaction.t()]
def address_to_transactions_with_token_tranfers(address_hash, token_hash, options \\ []) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hash
|> Transaction.transactions_with_token_transfers(token_hash)
|> Transaction.preload_token_transfers(address_hash)
|> handle_paging_options(paging_options)
|> Repo.all()
end
@doc """
The average time it took to mine/validate the last <= 100 `t:Explorer.Chain.Block.t/0`
"""

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Transaction do
use Explorer.Schema
import Ecto.Query, only: [from: 2, preload: 3, where: 3]
import Ecto.Query, only: [from: 2, preload: 3, where: 3, subquery: 1]
alias Ecto.Changeset
@ -16,6 +16,7 @@ defmodule Explorer.Chain.Transaction do
InternalTransaction,
Log,
TokenTransfer,
Transaction,
Wei
}
@ -503,4 +504,30 @@ defmodule Explorer.Chain.Transaction do
changeset
end
end
@doc """
Builds an `Ecto.Query` to fetch transactions with token transfers from the give address hash.
The results will be ordered by block number and index DESC.
"""
def transactions_with_token_transfers(address_hash, token_hash) do
query = transactions_with_token_transfers_query(address_hash, token_hash)
from(
t in subquery(query),
order_by: [desc: t.block_number, desc: t.index],
preload: [:from_address, :to_address, :created_contract_address, :block]
)
end
defp transactions_with_token_transfers_query(address_hash, token_hash) do
from(
t in Transaction,
inner_join: tt in TokenTransfer,
on: t.hash == tt.transaction_hash,
where: tt.token_contract_address_hash == ^token_hash,
where: tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash,
distinct: :hash
)
end
end

@ -37,4 +37,144 @@ defmodule Explorer.Chain.TransactionTest do
assert %Changeset{valid?: true} = Transaction.changeset(%Transaction{}, changeset_params)
end
end
describe "transactions_with_token_transfers/2" do
test "returns the transaction when there is token transfer from the given address" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
from_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
test "returns the transaction when there is token transfer to the given address" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
test "returns only transactions that have token transfers from the given token hash" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token.contract_address
)
insert(
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: insert(:token).contract_address
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
test "order the results DESC by block_number" do
address = insert(:address)
token = insert(:token)
transaction_a =
:transaction
|> insert()
|> with_block(insert(:block, number: 1000))
transaction_b =
:transaction
|> insert()
|> with_block(insert(:block, number: 1002))
transaction_c =
:transaction
|> insert()
|> with_block(insert(:block, number: 1003))
insert(
:token_transfer,
amount: 2,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction_a
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction_b
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction_c
)
result =
address.hash
|> Transaction.transactions_with_token_transfers(token.contract_address_hash)
|> Repo.all()
|> Enum.map(& &1.block_number)
assert result == [transaction_c.block_number, transaction_b.block_number, transaction_a.block_number]
end
end
end

@ -2716,4 +2716,80 @@ defmodule Explorer.ChainTest do
assert Chain.count_token_holders_from_token_hash(contract_address_hash) == 0
end
end
describe "address_to_transactions_with_token_tranfers/2" do
test "paginates transactions by the block number" do
address = insert(:address)
token = insert(:token)
first_page =
:transaction
|> insert()
|> with_block(insert(:block, number: 1000))
second_page =
:transaction
|> insert()
|> with_block(insert(:block, number: 999))
insert(
:token_transfer,
to_address: address,
transaction: first_page,
token_contract_address: token.contract_address
)
insert(
:token_transfer,
to_address: address,
transaction: second_page,
token_contract_address: token.contract_address
)
paging_options = %PagingOptions{
key: {first_page.block_number, first_page.index},
page_size: 2
}
result =
address.hash
|> Chain.address_to_transactions_with_token_tranfers(token.contract_address_hash, paging_options: paging_options)
|> Enum.map(& &1.hash)
assert result == [second_page.hash]
end
test "doesn't duplicate the transaction when there are multiple transfers for it" do
address = insert(:address)
token = insert(:token)
transaction =
:transaction
|> insert()
|> with_block()
insert(
:token_transfer,
amount: 2,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction
)
insert(
:token_transfer,
amount: 1,
to_address: address,
token_contract_address: token.contract_address,
transaction: transaction
)
result =
address.hash
|> Chain.address_to_transactions_with_token_tranfers(token.contract_address_hash)
|> Enum.map(& &1.hash)
assert result == [transaction.hash]
end
end
end

Loading…
Cancel
Save