Merge branch 'master' into master

pull/594/head
Mulili Nzuki 6 years ago committed by GitHub
commit a101cd5327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      apps/block_scout_web/assets/css/app.scss
  2. 7
      apps/block_scout_web/assets/css/components/_animations.scss
  3. 6
      apps/block_scout_web/assets/css/components/_card.scss
  4. 1
      apps/block_scout_web/assets/css/components/_tile.scss
  5. 62
      apps/block_scout_web/assets/css/components/_token_tile_view_more.scss
  6. 3
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss
  7. 2
      apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
  8. 1
      apps/block_scout_web/assets/js/app.js
  9. 20
      apps/block_scout_web/assets/js/lib/token_transfers_toggle.js
  10. 1
      apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg
  11. 1
      apps/block_scout_web/assets/static/images/classic_logo.svg
  12. 82
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  13. 146
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  14. 2
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  15. 4
      apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex
  16. 10
      apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex
  17. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  18. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  19. 99
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/_transaction.html.eex
  20. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  21. 4
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/_model_table.html.eex
  22. 51
      apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
  23. 25
      apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex
  24. 2
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  25. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex
  26. 67
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex
  27. 22
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_token_transfer.html.eex
  28. 60
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  29. 28
      apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
  30. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex
  31. 6
      apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex
  32. 40
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  33. 8
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  34. 11
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  35. 70
      apps/block_scout_web/priv/gettext/default.pot
  36. 70
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  37. 6
      apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs
  38. 200
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  39. 23
      apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex
  40. 8
      apps/block_scout_web/test/block_scout_web/features/pages/block_page.ex
  41. 8
      apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex
  42. 95
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  43. 102
      apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
  44. 26
      apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs
  45. 6
      apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
  46. 12
      apps/explorer/lib/explorer/chain.ex
  47. 18
      apps/explorer/lib/explorer/chain/address.ex
  48. 10
      apps/explorer/lib/explorer/chain/address/coin_balance.ex
  49. 40
      apps/explorer/lib/explorer/chain/import.ex
  50. 22
      apps/explorer/lib/explorer/etherscan.ex
  51. 4
      apps/explorer/priv/repo/migrations/20180117221921_create_address.exs
  52. 8
      apps/explorer/priv/repo/migrations/20180717204948_create_address_coin_balances.exs
  53. 8
      apps/explorer/test/explorer/chain/address/coin_balance_test.exs
  54. 129
      apps/explorer/test/explorer/chain_test.exs
  55. 45
      apps/explorer/test/explorer/etherscan_test.exs
  56. 2
      apps/explorer/test/explorer/import_test.exs
  57. 17
      apps/explorer/test/explorer/token/balance_reader_test.exs
  58. 10
      apps/explorer/test/support/factory.ex
  59. 4
      apps/indexer/lib/indexer/address/coin_balances.ex
  60. 71
      apps/indexer/lib/indexer/address_extraction.ex
  61. 4
      apps/indexer/lib/indexer/application.ex
  62. 16
      apps/indexer/lib/indexer/block_fetcher.ex
  63. 4
      apps/indexer/lib/indexer/block_fetcher/catchup.ex
  64. 2
      apps/indexer/lib/indexer/block_fetcher/realtime.ex
  65. 8
      apps/indexer/lib/indexer/coin_balance_fetcher.ex
  66. 6
      apps/indexer/lib/indexer/internal_transaction_fetcher.ex
  67. 20
      apps/indexer/test/indexer/address/coin_balances_test.exs
  68. 26
      apps/indexer/test/indexer/address_extraction_test.exs
  69. 8
      apps/indexer/test/indexer/block_fetcher/catchup/supervisor_test.exs
  70. 10
      apps/indexer/test/indexer/block_fetcher/realtime_test.exs
  71. 44
      apps/indexer/test/indexer/block_fetcher_test.exs
  72. 53
      apps/indexer/test/indexer/coin_balance_fetcher_test.exs
  73. 8
      apps/indexer/test/indexer/internal_transaction_fetcher_test.exs
  74. 8
      apps/indexer/test/support/indexer/coin_balance_fetcher_case.ex

@ -78,6 +78,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/nounderline-link";
@import "components/token-balance-dropdown";
@import "components/address-overview";
@import "components/token_tile_view_more";
:export {
primary: $primary;

@ -1,6 +1,7 @@
@keyframes fade-in {
0% {opacity: 0;}
100% {opacity: 1;}
0% {transform: scale(0.97); opacity: 0;}
50% {transform: scale(1);}
100% {transform: scale(1); opacity: 1;}
}
@keyframes fade-up-blocks-chain {
@ -60,7 +61,7 @@
}
.fade-in {
animation: fade-in 1s ease-out forwards;
animation: fade-in 0.6s ease-out forwards;
}
.fade-up-blocks-chain {

@ -36,9 +36,3 @@
@media (max-width: 767px) { height: 595px; }
}
.card-chain-transactions {
height: 694px;
@media (max-width: 767px) { height: 1231px; }
}

@ -50,6 +50,7 @@
&-token {
border-left: 4px solid $orange;
padding-bottom: 10px;
.tile-label {
color: $orange;

@ -0,0 +1,62 @@
.token-tile-view-more {
line-height: 0.5;
text-align: center;
&:hover, :focus {
text-decoration: none;
}
}
.token-tile-view-more span {
display: inline-block;
position: relative;
}
.token-tile-view-more span:before,
.token-tile-view-more span:after {
content: "";
position: absolute;
height: 5px;
border-bottom: 1px solid $border-color;
border-top: 1px solid $border-color;
top: 0;
width: 43%;
}
.token-tile-view-more span:before {
right: 55%;
margin-right: .9375rem;
}
.token-tile-view-more span:after {
left: 55%;
margin-left: .9375rem;
}
@include media-breakpoint-down(md) {
.token-tile-view-more span:before,
.token-tile-view-more span:after {
width: 40%;
}
.token-tile-view-more span:before {
right: 58%;
}
.token-tile-view-more span:after {
left: 58%;
}
}
@include media-breakpoint-down(sm) {
.token-tile-view-more span:before,
.token-tile-view-more span:after {
width: 28%;
}
.token-tile-view-more span:before {
right: 66%;
}
.token-tile-view-more span:after {
left: 66%;
}
}

@ -0,0 +1,3 @@
$primary: #1b1b3a;
$secondary: #40ed9e;
$tertiary: #40ed9e;

@ -1,5 +1,3 @@
$logo-file-path: url("/../images/ethereum_logo.svg");
$primary: #12455b;
$secondary: #4786cb;
$tertiary: #77a4c5;

@ -31,6 +31,7 @@ import './lib/smart_contract/wei_ether_converter'
import './lib/pretty_json'
import './lib/try_api'
import './lib/token_balance_dropdown'
import './lib/token_transfers_toggle'
import './pages/address'
import './pages/block'

@ -0,0 +1,20 @@
import $ from 'jquery'
const tokenTransferToggle = (element) => {
const $element = $(element)
const $tokenTransferClose = $element.find("[data-selector='token-transfer-close']")
const $tokenTransferOpen = $element.find("[data-selector='token-transfer-open']")
$element.on('show.bs.collapse', () => {
$tokenTransferOpen.addClass('d-none')
$tokenTransferClose.removeClass('d-none')
})
$element.on('hide.bs.collapse', () => {
$tokenTransferClose.addClass('d-none')
$tokenTransferOpen.removeClass('d-none')
})
}
// Initialize the script scoped for each card.
$("[data-selector='token-transfers-toggle']").each((_index, element) => tokenTransferToggle(element))

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 425 106"><defs><style>.cls-1{fill:#fff;}</style></defs><title>classic_logo</title><path class="cls-1" d="M124.947,72.479a10.032,10.032,0,0,0,6.65-2.238,7.874,7.874,0,0,0,2.9-5.792h3.65a10.753,10.753,0,0,1-1.967,5.729,12.32,12.32,0,0,1-4.809,4.047,14.494,14.494,0,0,1-6.427,1.46,13.267,13.267,0,0,1-10.712-4.7q-3.949-4.7-3.951-12.632V57.213a21.45,21.45,0,0,1,1.778-8.982,13.688,13.688,0,0,1,5.093-6.03,14.061,14.061,0,0,1,7.761-2.127,13.242,13.242,0,0,1,9.314,3.364,12.265,12.265,0,0,1,3.92,8.855H134.5a9.188,9.188,0,0,0-9.584-8.981,9.467,9.467,0,0,0-7.982,3.713q-2.841,3.712-2.841,10.411v1.11q0,6.57,2.841,10.252A9.532,9.532,0,0,0,124.947,72.479Z"/><path class="cls-1" d="M163.184,75.05h-3.809V26.3h3.809Z"/><path class="cls-1" d="M208.371,75.05a17.958,17.958,0,0,1-.73-4.792,13.237,13.237,0,0,1-5.094,4.014,15.6,15.6,0,0,1-6.554,1.413,11.511,11.511,0,0,1-8.014-2.761,8.994,8.994,0,0,1-3.062-6.983,9.191,9.191,0,0,1,4.173-7.934q4.173-2.92,11.632-2.921h6.887v-3.9a7.522,7.522,0,0,0-2.269-5.792q-2.27-2.112-6.617-2.111a10.357,10.357,0,0,0-6.57,2.031,6.034,6.034,0,0,0-2.6,4.888l-3.808-.032a8.833,8.833,0,0,1,3.808-7.094,14.668,14.668,0,0,1,9.363-3q5.745,0,9.061,2.872a10.308,10.308,0,0,1,3.412,8.014V67.211a20.132,20.132,0,0,0,1.048,7.458v.381Zm-11.933-2.73a12.745,12.745,0,0,0,6.808-1.841,10.826,10.826,0,0,0,4.363-4.918V58.007h-6.792q-5.68.063-8.886,2.078a6.24,6.24,0,0,0-3.206,5.539,6.137,6.137,0,0,0,2.143,4.792A8.065,8.065,0,0,0,196.438,72.32Z"/><path class="cls-1" d="M255.272,66.322A5.413,5.413,0,0,0,253,61.783a17.5,17.5,0,0,0-6.839-2.681,30.851,30.851,0,0,1-7.093-2.222,9.442,9.442,0,0,1-3.745-3.063,7.638,7.638,0,0,1-1.222-4.38,8.166,8.166,0,0,1,3.4-6.712,13.712,13.712,0,0,1,8.7-2.651q5.744,0,9.22,2.841a9.117,9.117,0,0,1,3.475,7.411h-3.808a6.149,6.149,0,0,0-2.524-5.015,11.391,11.391,0,0,0-12.378-.333,5.141,5.141,0,0,0-2.269,4.332,4.684,4.684,0,0,0,1.888,3.983q1.888,1.413,6.888,2.587a32.363,32.363,0,0,1,7.49,2.508,9.706,9.706,0,0,1,3.7,3.205,8.815,8.815,0,0,1-2.3,11.489,14.987,14.987,0,0,1-9.157,2.6,15.377,15.377,0,0,1-9.728-2.905,8.936,8.936,0,0,1-3.729-7.346h3.809a6.6,6.6,0,0,0,2.809,5.188,11.478,11.478,0,0,0,6.839,1.857,10.789,10.789,0,0,0,6.411-1.746A5.237,5.237,0,0,0,255.272,66.322Z"/><path class="cls-1" d="M301.379,66.322a5.413,5.413,0,0,0-2.27-4.539A17.5,17.5,0,0,0,292.27,59.1a30.851,30.851,0,0,1-7.093-2.222,9.442,9.442,0,0,1-3.745-3.063,7.631,7.631,0,0,1-1.223-4.38,8.169,8.169,0,0,1,3.4-6.712,13.717,13.717,0,0,1,8.7-2.651q5.743,0,9.219,2.841A9.115,9.115,0,0,1,305,50.326h-3.809a6.148,6.148,0,0,0-2.523-5.015,11.391,11.391,0,0,0-12.378-.333,5.141,5.141,0,0,0-2.269,4.332,4.682,4.682,0,0,0,1.888,3.983q1.889,1.413,6.888,2.587a32.363,32.363,0,0,1,7.49,2.508,9.7,9.7,0,0,1,3.7,3.205,8.812,8.812,0,0,1-2.3,11.489,14.989,14.989,0,0,1-9.158,2.6A15.376,15.376,0,0,1,282.8,72.78a8.937,8.937,0,0,1-3.73-7.346h3.809a6.6,6.6,0,0,0,2.81,5.188,11.472,11.472,0,0,0,6.838,1.857,10.8,10.8,0,0,0,6.413-1.746A5.238,5.238,0,0,0,301.379,66.322Z"/><path class="cls-1" d="M326.7,30.791a2.55,2.55,0,0,1,.7-1.814,2.49,2.49,0,0,1,1.9-.741,2.492,2.492,0,0,1,2.634,2.555,2.463,2.463,0,0,1-.714,1.8,2.574,2.574,0,0,1-1.92.726,2.517,2.517,0,0,1-1.9-.726A2.491,2.491,0,0,1,326.7,30.791Zm4.476,44.259h-3.809V40.709h3.809Z"/><path class="cls-1" d="M367.379,72.479a10.029,10.029,0,0,0,6.649-2.238,7.875,7.875,0,0,0,2.9-5.792h3.65a10.754,10.754,0,0,1-1.968,5.729,12.317,12.317,0,0,1-4.808,4.047,14.494,14.494,0,0,1-6.427,1.46,13.267,13.267,0,0,1-10.712-4.7q-3.951-4.7-3.951-12.632V57.213a21.466,21.466,0,0,1,1.777-8.982,13.7,13.7,0,0,1,5.094-6.03,14.061,14.061,0,0,1,7.761-2.127,13.244,13.244,0,0,1,9.314,3.364,12.273,12.273,0,0,1,3.92,8.855h-3.65a9.189,9.189,0,0,0-9.584-8.981,9.47,9.47,0,0,0-7.983,3.713q-2.841,3.712-2.841,10.411v1.11q0,6.57,2.841,10.252A9.536,9.536,0,0,0,367.379,72.479Z"/><path class="cls-1" d="M81.333,52.419v2.266a3.585,3.585,0,0,0-.518.512Q69.736,71.745,58.672,88.3H55.65Q45.524,73.15,35.389,58.006a17.114,17.114,0,0,0-2.4-3.321V52.419a3.645,3.645,0,0,0,.519-.512Q44.585,35.359,55.65,18.8h3.022Q68.8,33.954,78.933,49.1A17.067,17.067,0,0,0,81.333,52.419ZM69.947,61.048c-3.775,1.823-7.574,3.6-11.31,5.5a2.869,2.869,0,0,1-2.953,0c-3.736-1.9-7.535-3.678-11.311-5.5-.078-.073-.206-.221-.226-.207-.233.156-.095.271.063.375Q50.348,70.5,56.48,79.781c.493.748.867.75,1.361,0q6.123-9.291,12.27-18.566c.159-.1.3-.22.064-.375C70.154,60.827,70.026,60.975,69.947,61.048Zm0-14.992c.078.073.207.221.227.207.233-.156.094-.271-.064-.375q-6.138-9.28-12.27-18.565c-.492-.748-.866-.75-1.36,0Q50.358,36.613,44.21,45.887c-.158.1-.3.22-.063.376.02.014.149-.134.227-.207,3.775-1.824,7.575-3.6,11.311-5.5a2.865,2.865,0,0,1,2.952,0C62.374,42.456,66.173,44.232,69.948,46.056Zm-28.2,7.5c3.666,1.827,7.127,3.193,10.19,5.174,3.642,2.354,6.792,2.377,10.44.007,3.061-1.987,6.527-3.351,10.2-5.183-3.666-1.828-7.127-3.193-10.19-5.174-3.641-2.354-6.792-2.377-10.44-.008C48.886,50.357,45.419,51.72,41.745,53.553Z"/></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

@ -93,6 +93,23 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end
end
@tokenbalance_required_params ~w(contractaddress address)
def tokenbalance(conn, params) do
with {:required_params, {:ok, fetched_params}} <- fetch_required_params(params, @tokenbalance_required_params),
{:format, {:ok, validated_params}} <- to_valid_format(fetched_params, :tokenbalance) do
token_balance = get_token_balance(validated_params)
render(conn, "tokenbalance.json", %{token_balance: token_balance})
else
{:required_params, {:error, missing_params}} ->
error = "Required query parameters missing: #{Enum.join(missing_params, ", ")}"
render(conn, :error, error: error)
{:format, {:error, param}} ->
render(conn, :error, error: "Invalid #{param} format")
end
end
def getminedblocks(conn, params) do
options = put_pagination_options(%{}, params)
@ -112,6 +129,11 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
end
end
@doc """
Sanitizes optional params.
"""
@spec optional_params(map()) :: map()
def optional_params(params) do
%{}
|> put_order_by_direction(params)
@ -121,6 +143,50 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
|> put_filter_by(params)
end
@doc """
Fetches required params. Returns error tuple if required params are missing.
"""
@spec fetch_required_params(map(), list()) :: {:required_params, {:ok, map()} | {:error, [String.t(), ...]}}
def fetch_required_params(params, required_params) do
fetched_params = Map.take(params, required_params)
result =
if all_of_required_keys_found?(fetched_params, required_params) do
{:ok, fetched_params}
else
missing_params = get_missing_required_params(fetched_params, required_params)
{:error, missing_params}
end
{:required_params, result}
end
defp to_valid_format(params, :tokenbalance) do
result =
with {:ok, contract_address_hash} <- to_address_hash(params, "contractaddress"),
{:ok, address_hash} <- to_address_hash(params, "address") do
{:ok, %{contract_address_hash: contract_address_hash, address_hash: address_hash}}
else
{:error, _param_key} = error -> error
end
{:format, result}
end
defp all_of_required_keys_found?(fetched_params, required_params) do
Enum.all?(required_params, &Map.has_key?(fetched_params, &1))
end
defp get_missing_required_params(fetched_params, required_params) do
fetched_keys = fetched_params |> Map.keys() |> MapSet.new()
required_params
|> MapSet.new()
|> MapSet.difference(fetched_keys)
|> MapSet.to_list()
end
defp fetch_address(params) do
{:address_param, Map.fetch(params, "address")}
end
@ -179,7 +245,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
Enum.map(hashes, fn hash ->
%Address{
hash: hash,
fetched_balance: %Wei{value: 0}
fetched_coin_balance: %Wei{value: 0}
}
end)
end
@ -194,6 +260,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
{:format, Chain.string_to_address_hash(address_hash_string)}
end
defp to_address_hash(params, param_key) do
case Chain.string_to_address_hash(params[param_key]) do
{:ok, address_hash} -> {:ok, address_hash}
:error -> {:error, param_key}
end
end
defp to_transaction_hash(transaction_hash_string) do
{:format, Chain.string_to_transaction_hash(transaction_hash_string)}
end
@ -284,4 +357,11 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
blocks -> {:ok, blocks}
end
end
defp get_token_balance(%{contract_address_hash: contract_address_hash, address_hash: address_hash}) do
case Etherscan.get_token_balance(contract_address_hash, address_hash) do
nil -> 0
token_balance -> token_balance.value
end
end
end

@ -130,6 +130,18 @@ defmodule BlockScoutWeb.Etherscan do
"result" => []
}
@account_tokenbalance_example_value %{
"status" => "1",
"message" => "OK",
"result" => "135499"
}
@account_tokenbalance_example_value_error %{
"status" => "0",
"message" => "Invalid address format",
"result" => nil
}
@account_getminedblocks_example_value %{
"status" => "1",
"message" => "OK",
@ -203,6 +215,25 @@ defmodule BlockScoutWeb.Etherscan do
"result" => "21265524714464"
}
@block_getblockreward_example_value %{
"status" => "1",
"message" => "OK",
"result" => %{
"blockNumber" => "2165403",
"timeStamp" => "1472533979",
"blockMiner" => "0x13a06d3dfe21e0db5c016c03ea7d2509f7f8d1e3",
"blockReward" => "5314181600000000000",
"uncles" => nil,
"uncleInclusionReward" => nil
}
}
@block_getblockreward_example_value_error %{
"status" => "0",
"message" => "Invalid block number",
"result" => nil
}
@status_type %{
type: "status",
enum: ~s(["0", "1"]),
@ -492,6 +523,26 @@ defmodule BlockScoutWeb.Etherscan do
}
}
@block_reward_model %{
name: "BlockReward",
fields: %{
blockNumber: @block_number_type,
timeStamp: %{
type: "timestamp",
definition: "When the block was collated.",
example: ~s("1480072029")
},
blockMiner: @address_hash_type,
blockReward: %{
type: "block reward",
definition: "The reward given to the miner of a block.",
example: ~s("5003251945421042780")
},
uncles: %{type: "null"},
uncleInclusionReward: %{type: "null"}
}
}
@account_balance_action %{
name: "balance",
description: "Get balance for address",
@ -748,6 +799,50 @@ defmodule BlockScoutWeb.Etherscan do
]
}
@account_tokenbalance_action %{
name: "tokenbalance",
description: "Get token account balance for token contract address.",
required_params: [
%{
key: "contractaddress",
placeholder: "contractAddressHash",
type: "string",
description: "A 160-bit code used for identifying contracts."
},
%{
key: "address",
placeholder: "addressHash",
type: "string",
description: "A 160-bit code used for identifying accounts."
}
],
optional_params: [],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@account_tokenbalance_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "integer",
definition: "The token account balance for the contract address.",
example: ~s("135499")
}
}
}
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@account_tokenbalance_example_value_error)
}
]
}
@account_getminedblocks_action %{
name: "getminedblocks",
description: "Get list of blocks mined by address.",
@ -993,6 +1088,43 @@ defmodule BlockScoutWeb.Etherscan do
]
}
@block_getblockreward_action %{
name: "getblockreward",
description: "Get block reward by block number.",
required_params: [
%{
key: "blockno",
placeholder: "blockNumber",
type: "integer",
description: "A nonnegative integer that represents the block number."
}
],
optional_params: [],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@block_getblockreward_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "model",
model: @block_reward_model
}
}
}
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@block_getblockreward_example_value_error)
}
]
}
@account_module %{
name: "account",
actions: [
@ -1001,6 +1133,7 @@ defmodule BlockScoutWeb.Etherscan do
@account_txlist_action,
@account_txlistinternal_action,
@account_tokentx_action,
@account_tokenbalance_action,
@account_getminedblocks_action
]
}
@ -1020,7 +1153,18 @@ defmodule BlockScoutWeb.Etherscan do
actions: [@stats_tokensupply_action]
}
@documentation [@account_module, @logs_module, @token_module, @stats_module]
@block_module %{
name: "block",
actions: [@block_getblockreward_action]
}
@documentation [
@account_module,
@logs_module,
@token_module,
@stats_module,
@block_module
]
def get_documentation do
@documentation

@ -13,7 +13,7 @@ defmodule BlockScoutWeb.Notifier do
Endpoint.broadcast("addresses:new_address", "count", %{count: address_count_module.address_estimated_count()})
addresses
|> Stream.reject(fn %Address{fetched_balance: fetched_balance} -> is_nil(fetched_balance) end)
|> Stream.reject(fn %Address{fetched_coin_balance: fetched_coin_balance} -> is_nil(fetched_coin_balance) end)
|> Enum.each(&broadcast_balance/1)
end

@ -1,5 +1,5 @@
<%= if @address_hash do %>
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @locale, @address_hash), "data-address-hash": @address_hash, "data-test": "address_hash_link" do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: @address_hash, contract: @contract %>
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @locale, @address_hash), "data-test": "address_hash_link" do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: @address_hash, contract: @contract, truncate: assigns[:truncate] %>
<% end %>
<% end %>

@ -1,4 +1,8 @@
<span class="<%= if @contract do %>contract-address<% end %>">
<span class="d-none d-md-none d-lg-inline"><%= @address_hash %></span>
<span class="d-md-inline-block d-lg-none "><%= BlockScoutWeb.AddressView.trimmed_hash(@address_hash) %></span>
<span class="<%= if @contract do %>contract-address<% end %>" data-address-hash="<%= @address_hash %>">
<%= if assigns[:truncate] do %>
<%= BlockScoutWeb.AddressView.trimmed_hash(@address_hash) %>
<% else %>
<span class="d-none d-md-none d-lg-inline"><%= @address_hash %></span>
<span class="d-md-inline-block d-lg-none"><%= BlockScoutWeb.AddressView.trimmed_hash(@address_hash) %></span>
<% end%>
</span>

@ -16,7 +16,7 @@
</span>
</div>
<h1 class="card-title"><%= address_title(@address) %> Details </h1>
<h3 class="<%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
<h3 class="<%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address.hash %></h3>
<div class="d-flex flex-row flex-md-column justify-content-start text-muted">
<span class="mr-4 mb-md-2"><span data-selector="transaction-count"><%= Cldr.Number.to_string!(@transaction_count, format: "#,###") %></span> <%= gettext "Transactions" %></span>

@ -130,7 +130,7 @@
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm float-right",
class: "button button--secondary button--sm float-right mt-3",
to: address_internal_transaction_path(
@conn,
:index,

@ -1,99 +0,0 @@
<div class="tile tile-type-<%= BlockScoutWeb.TransactionView.type_suffix(@transaction) %> fade-in tile-status--<%= BlockScoutWeb.TransactionView.status(@transaction) %>" data-transaction-hash="<%= @transaction.hash %>">
<div class="row">
<div class="col-md-2 d-flex flex-row flex-md-column align-items-left justify-content-start justify-content-lg-center mb-1 mb-md-0 pl-md-4">
<span class="tile-label">
<%= BlockScoutWeb.TransactionView.transaction_display_type(@transaction) %>
</span>
<span class="tile-status-label ml-2 ml-md-0" data-test="transaction_status">
<%= BlockScoutWeb.TransactionView.formatted_status(@transaction) %>
</span>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<%= render BlockScoutWeb.TransactionView, "_link.html", locale: @locale, transaction_hash: @transaction.hash %>
<span class="text-nowrap">
<%= if @address.hash == @transaction.from_address_hash do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: @transaction.from_address_hash, contract: BlockScoutWeb.AddressView.contract?(@transaction.from_address) %>
<% else %>
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @transaction.from_address_hash, contract: BlockScoutWeb.AddressView.contract?(@transaction.from_address), locale: @locale %>
<% end %>
&rarr;
<%= if @address.hash == BlockScoutWeb.TransactionView.to_address_hash(@transaction) do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: BlockScoutWeb.TransactionView.to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address) %>
<% else %>
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: BlockScoutWeb.TransactionView.to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address), locale: @locale %>
<% end %>
</span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="tile-title">
<%= BlockScoutWeb.TransactionView.value(@transaction, include_label: false) %> <%= gettext "Ether" %>
</span>
<span class="ml-0 ml-md-1 text-nowrap">
<%= BlockScoutWeb.TransactionView.formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %>
</span>
</span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-start text-md-right mt-3 mt-md-0">
<span class="mr-2 mr-md-0 order-1">
<%= link(
gettext("Block #%{number}", number: to_string(@transaction.block.number)),
to: block_path(BlockScoutWeb.Endpoint, :show, @locale, @transaction.block)
) %>
</span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= @transaction.block.timestamp %>"></span>
<%= if from_or_to_address?(@transaction, @address) do %>
<span class="mr-2 mr-md-0 order-0 order-md-3">
<%= if @transaction.from_address_hash == @address.hash do %>
<span data-test="transaction_type" class="badge badge-danger tile-badge">
<%= gettext "OUT" %>
</span>
<% else %>
<span data-test="transaction_type" class="badge badge-success tile-badge">
<%= gettext "IN" %>
</span>
<% end %>
</span>
<% end %>
</div>
<%= if BlockScoutWeb.TransactionView.involves_token_transfers?(@transaction) do %>
<div class="offset-md-2 col-md-10">
<hr class="mt-3 mb-3 w-100" />
<p class="tile-title"><%= gettext "Transfers" %></p>
</div>
<% end %>
<%= for token_transfer <- @transaction.token_transfers do %>
<div class="offset-md-2 col-md-7 col-lg-8 d-flex flex-column mt-1 mb-2">
<span class="text-nowrap" data-test="token_transfer">
<span data-test="token_transfer_address_hash">
<%= BlockScoutWeb.AddressView.display_address_hash(@address, token_transfer.from_address, @locale) %>
</span>
&rarr;
<span data-test="token_transfer_address_hash">
<%= BlockScoutWeb.AddressView.display_address_hash(@address, token_transfer.to_address, @locale) %>
</span>
</span>
<span class="tile-title">
<%= 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 class="col-md-3 col-lg-2 d-flex flex-row flex-md-column align-items-end">
<%= if from_or_to_address?(token_transfer, @address) do %>
<%= if token_transfer.from_address_hash == @address.hash do %>
<span data-test="transaction_type" class="badge badge-danger tile-badge">
<%= gettext "OUT" %>
</span>
<% else %>
<span data-test="transaction_type" class="badge badge-success tile-badge">
<%= gettext "IN" %>
</span>
<% end %>
<% end %>
</div>
<% end %>
</div>
</div>

@ -131,7 +131,7 @@
<h2 class="card-title"><%= gettext "Transactions" %></h2>
<span data-selector="transactions-list">
<%= for transaction <- @transactions do %>
<%= render("_transaction.html", locale: @locale, address: @address, transaction: transaction, conn: @conn) %>
<%= render(BlockScoutWeb.TransactionView, "_tile.html", locale: @locale, current_address: @address, transaction: transaction) %>
<% end %>
</span>
<% else %>
@ -143,7 +143,7 @@
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm float-right",
class: "button button--secondary button--sm float-right mt-3",
to: address_transaction_path(
@conn,
:index,

@ -2,10 +2,10 @@
<h3><strong><%= @model.name %></strong> {</h3>
<%= for {key, details} <- @model.fields do %>
<dl class="row">
<dt class="col-md-2">
<dt class="col-md-3">
<%= key %>
</dt>
<dd class="col-md-10">
<dd class="col-md-9">
<span>
<%= if details[:type] != "model", do: details.type %>
<%= if details[:definition] do %>

@ -63,38 +63,33 @@
</div>
</div>
<div class="col-md-12 col-lg-4">
<div class="row">
<div class="col-md-6 col-lg-12">
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Validator -->
<div class="card card-primary">
<div class="card-body">
<h2 class="card-title text-white"> <%= gettext "Miner" %> </h2>
<div class="text-right">
<!-- Validator's Name -->
<!-- Until we can get the validator's name we are using the Validator's address hash -->
<h3 class="text-white text-truncate"> <%= link @block.miner, class: "text-white", to: address_path(BlockScoutWeb.Endpoint, :show, @locale, @block.miner) %> </h3>
<!-- Validator's address hash -->
<span class="text-light text-truncate"> </span>
</div>
</div>
<div class="card card-primary flex-grow-1">
<div class="card-body">
<h2 class="card-title text-white"> <%= gettext "Miner" %> </h2>
<div class="text-right">
<!-- Validator's Name -->
<!-- Until we can get the validator's name we are using the Validator's address hash -->
<h3 class="text-white text-truncate"> <%= link @block.miner, class: "text-white", to: address_path(BlockScoutWeb.Endpoint, :show, @locale, @block.miner) %> </h3>
<!-- Validator's address hash -->
<span class="text-light text-truncate"> </span>
</div>
</div>
<div class="col-md-6 col-lg-12">
</div>
<!-- Gas -->
<div class="card">
<div class="card-body">
<h2 class="card-title"> <%= gettext "Gas Used" %> </h2>
<div class="text-right">
<!-- Gas Used -->
<h3>
<%= @block.gas_used |> Cldr.Number.to_string! %>
<span class="text-muted"> (<%= (@block.gas_used / @block.gas_limit) |> Cldr.Number.to_string!(format: "#.#%") %>) </span>
</h3>
<!-- Gas Limit -->
<span class="text-muted"> <%= @block.gas_limit |> Cldr.Number.to_string! %> <%= gettext "Gas Limit" %> </span>
</div>
</div>
<div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0">
<div class="card-body">
<h2 class="card-title"> <%= gettext "Gas Used" %> </h2>
<div class="text-right">
<!-- Gas Used -->
<h3>
<%= @block.gas_used |> Cldr.Number.to_string! %>
<span class="text-muted"> (<%= (@block.gas_used / @block.gas_limit) |> Cldr.Number.to_string!(format: "#.#%") %>) </span>
</h3>
<!-- Gas Limit -->
<span class="text-muted"> <%= @block.gas_limit |> Cldr.Number.to_string! %> <%= gettext "Gas Limit" %> </span>
</div>
</div>
</div>

@ -44,19 +44,20 @@
<span data-selector="empty-transactions-list"><%= gettext "There are no transactions for this address." %></span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm float-right mt-3",
to: transaction_path(
@conn,
:index,
@conn.assigns.locale,
@next_page_params
)
) %>
<% end %>
</div>
</div>
<%= if @next_page_params do %>
<%= link(
gettext("Older"),
class: "button button--secondary button--sm u-float-right mt-3",
to: transaction_path(
@conn,
:index,
@conn.assigns.locale,
@next_page_params
)
) %>
<% end %>
</section>
</section>

@ -63,7 +63,7 @@
</div>
</div>
</div>
<!-- We hardcoded the height on this element to keep the page from bouncing during the intro animation. -->
<div class="card card-chain-transactions">
<div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">

@ -30,7 +30,7 @@
</span>
</div>
<div class="p-4 col-md-2 col-lg-2 d-flex flex-row flex-md-column justify-content-start align-items-end text-md-right">
<div class="col-md-2 col-lg-2 d-flex flex-row flex-md-column justify-content-start align-items-end text-md-right">
<span class="ml-1 mr-sm-0 text-muted" data-from-now="<%= @transfer.transaction.block.timestamp %>"></span>
<span class="ml-2">

@ -1,35 +1,74 @@
<div class="tile tile-type-<%= BlockScoutWeb.TransactionView.type_suffix(@transaction) %> tile-status--<%= BlockScoutWeb.TransactionView.status(@transaction) %> fade-up" data-test="<%= BlockScoutWeb.TransactionView.type_suffix(@transaction) %>" data-transaction-hash="<%= @transaction.hash %>">
<div class="row" data-test="chain_transaction">
<div class="tile tile-type-<%= type_suffix(@transaction) %> fade-in tile-status--<%= status(@transaction) %>" data-test="<%= type_suffix(@transaction) %>" data-transaction-hash="<%= @transaction.hash %>">
<div class="row" data-selector="token-transfers-toggle" data-test="chain_transaction">
<div class="col-md-2 d-flex flex-row flex-md-column align-items-left justify-content-start justify-content-lg-center mb-1 mb-md-0 pl-md-4">
<span class="tile-label" data-test="transaction_type">
<%= BlockScoutWeb.TransactionView.transaction_display_type(@transaction) %>
<%= transaction_display_type(@transaction) %>
</span>
<span class="tile-status-label ml-2 ml-md-0" data-test="transaction_status">
<%= formatted_status(@transaction) %>
</span>
<div class="tile-status-label ml-2 ml-md-0" data-test="transaction_status">
<%= BlockScoutWeb.TransactionView.formatted_status(@transaction) %>
</div>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<%= render BlockScoutWeb.TransactionView, "_link.html", locale: @locale, transaction_hash: @transaction.hash %>
<%= render "_link.html", locale: @locale, transaction_hash: @transaction.hash %>
<span class="text-nowrap">
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: @transaction.from_address_hash, contract: BlockScoutWeb.AddressView.contract?(@transaction.from_address), locale: @locale %>
<%= BlockScoutWeb.AddressView.display_address_hash(assigns[:current_address], @transaction.from_address, @locale) %>
&rarr;
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: BlockScoutWeb.TransactionView.to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address), locale: @locale %>
<%= if assigns[:current_address] && assigns[:current_address].hash == to_address_hash(@transaction) do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address_hash: to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address) %>
<% else %>
<%= render BlockScoutWeb.AddressView, "_link.html", address_hash: to_address_hash(@transaction), contract: BlockScoutWeb.AddressView.contract?(@transaction.to_address), locale: @locale %>
<% end %>
</span>
<span class="d-flex flex-md-row flex-column mt-3 mt-md-0">
<span class="tile-title">
<%= BlockScoutWeb.TransactionView.value(@transaction, include_label: false) %> <%= gettext "Ether" %>
<%= value(@transaction, include_label: false) %> <%= gettext "Ether" %>
</span>
<span class="ml-0 ml-md-1 text-nowrap">
<%= formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %>
</span>
<span class="ml-0 ml-md-1 text-nowrap"> <%= BlockScoutWeb.TransactionView.formatted_fee(@transaction, denomination: :ether, include_label: false) %> <%= gettext "TX Fee" %></span>
</span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column justify-content-start text-md-right mt-3 mt-md-0">
<span class="mr-2 mr-md-0">
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-start text-md-right mt-3 mt-md-0">
<span class="mr-2 mr-md-0 order-1">
<%= link(
gettext("Block #%{number}", number: to_string(@transaction.block.number)),
to: block_path(BlockScoutWeb.Endpoint, :show, @locale, @transaction.block)
) %>
</span>
<span data-from-now="<%= @transaction.block.timestamp %>"></span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= @transaction.block.timestamp %>"></span>
<%= if from_or_to_address?(@transaction, assigns[:current_address]) do %>
<span class="mr-2 mr-md-0 order-0 order-md-3">
<%= if @transaction.from_address_hash == assigns[:current_address].hash do %>
<span data-test="transaction_type" class="badge badge-danger tile-badge">
<%= gettext "OUT" %>
</span>
<% else %>
<span data-test="transaction_type" class="badge badge-success tile-badge">
<%= gettext "IN" %>
</span>
<% end %>
</span>
<% end %>
</div>
<%= if involves_token_transfers?(@transaction) do %>
<div class="offset-md-2 col-md-10 col-lg-8 d-flex flex-column mt-2 mb-2">
<% [first_token_transfer | remaining_token_transfers]= @transaction.token_transfers %>
<%= render "_token_transfer.html", address: assigns[:current_address], locale: @locale, token_transfer: first_token_transfer %>
<div class="collapse token-transfer-toggle" id="<%= @transaction.hash %>">
<%= for token_transfer <- remaining_token_transfers do %>
<%= render "_token_transfer.html", address: assigns[:current_address], locale: @locale, token_transfer: token_transfer %>
<% end %>
</div>
</div>
<%= if Enum.any?(remaining_token_transfers) do %>
<div class="col-md-12 d-flex flex-column mt-1 mb-2 text-center token-tile-view-more">
<span class="token-tile-more-lines">
<%= link gettext("View More Transfers"), to: "##{@transaction.hash}", "data-toggle": "collapse", "data-selector": "token-transfer-open", "data-test": "token_transfers_expansion" %>
<%= link gettext("View Less Transfers"), class: "d-none", to: "##{@transaction.hash}", "data-toggle": "collapse", "data-selector": "token-transfer-close" %>
</span>
</div>
<% end %>
<% end %>
</div>
</div>

@ -0,0 +1,22 @@
<div class="text-nowrap row mt-3 mt-sm-0" data-test="token_transfer">
<span class="col-12 col-md-5">
<%= if from_or_to_address?(@token_transfer, @address) do %>
<%= if @token_transfer.from_address_hash == @address.hash do %>
<span data-test="transaction_type" class="text-danger">
&#8627;
</span>
<% else %>
<span data-test="transaction_type" class="text-success">
&#8627;
</span>
<% end %>
<% end %>
<%= BlockScoutWeb.AddressView.display_address_hash(@address, @token_transfer.from_address, @locale, true) %>
&rarr;
<%= BlockScoutWeb.AddressView.display_address_hash(@address, @token_transfer.to_address, @locale, true) %>
</span>
<span class="col-12 col-md-7 ml-3 ml-sm-0">
<%= token_transfer_amount(@token_transfer) %>
<%= link(token_symbol(@token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, @locale, @token_transfer.token.contract_address_hash)) %>
</span>
</div>

@ -75,42 +75,36 @@
</div>
</div>
<div class="col-md-12 col-lg-4">
<div class="row">
<div class="col-md-6 col-lg-12">
<!-- Value -->
<div class="card card-primary ">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "Ether" %> <%= gettext "Value" %></h2>
<div class="text-right">
<h3 class="text-white"> <%= value(@transaction) %></h3>
<span class="text-light"> <%= formatted_usd_value(@transaction, @exchange_rate) %></span>
</div>
</div>
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column">
<!-- Value -->
<div class="card card-primary flex-grow-1">
<div class="card-body">
<h2 class="card-title text-white"><%= gettext "Ether" %> <%= gettext "Value" %></h2>
<div class="text-right">
<h3 class="text-white"> <%= value(@transaction) %></h3>
<span class="text-light"> <%= formatted_usd_value(@transaction, @exchange_rate) %></span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-12">
<!-- Gas -->
<div class="card">
<div class="card-body">
<h2 class="card-title"> <%= gettext "Gas" %> </h2>
<div class="text-right">
<!-- Gas Used -->
<h3>
<span>
<%= gettext "Used" %>
<%= gas_used(@transaction) %> @
<%= gas_price(@transaction, :gwei) %>
</span>
</h3>
<!-- Gas Limit -->
<span class="text-muted">
<%= gettext "Limit" %>
<%= format_gas_limit(@transaction.gas) %>
</span>
</div>
</div>
<!-- Gas -->
<div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0">
<div class="card-body">
<h2 class="card-title"> <%= gettext "Gas" %> </h2>
<div class="text-right">
<!-- Gas Used -->
<h3>
<span>
<%= gettext "Used" %>
<%= gas_used(@transaction) %> @
<%= gas_price(@transaction, :gwei) %>
</span>
</h3>
<!-- Gas Limit -->
<span class="text-muted">
<%= gettext "Limit" %>
<%= format_gas_limit(@transaction.gas) %>
</span>
</div>
</div>
</div>

@ -71,19 +71,21 @@
<span><%= gettext "There are no internal transactions for this transaction." %></span>
</div>
<% end %>
<%= if @next_page_params do %>
<%= link(
gettext("Newer"),
class: "button button--secondary button--sm float-right mt-3",
to: transaction_internal_transaction_path(
@conn,
:index,
@conn.assigns.locale,
@transaction,
@next_page_params
)
) %>
<% end %>
</div>
</div>
<%= if @next_page_params do %>
<%= link(
gettext("Newer"),
class: "button button--secondary button--sm u-float-right mt-3",
to: transaction_internal_transaction_path(
@conn,
:index,
@conn.assigns.locale,
@transaction,
@next_page_params
)
) %>
<% end %>
</section>

@ -1,10 +1,10 @@
<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">
<div class="col-12 col-md-4 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">
<div class="col-12 col-md-8 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 %>

@ -1,8 +1,6 @@
defmodule BlockScoutWeb.AddressTransactionView do
use BlockScoutWeb, :view
alias Explorer.Chain.Address
import BlockScoutWeb.AddressView,
only: [contract?: 1, smart_contract_verified?: 1, smart_contract_with_read_only_functions?: 1]
@ -13,8 +11,4 @@ defmodule BlockScoutWeb.AddressTransactionView do
_ -> gettext("All")
end
end
def from_or_to_address?(%{from_address_hash: from_hash, to_address_hash: to_hash}, %Address{hash: hash}) do
from_hash == hash || to_hash == hash
end
end

@ -19,16 +19,16 @@ defmodule BlockScoutWeb.AddressView do
@doc """
Returns a formatted address balance and includes the unit.
"""
def balance(%Address{fetched_balance: nil}), do: ""
def balance(%Address{fetched_coin_balance: nil}), do: ""
def balance(%Address{fetched_balance: balance}) do
def balance(%Address{fetched_coin_balance: balance}) do
format_wei_value(balance, :ether)
end
def balance_block_number(%Address{fetched_balance_block_number: nil}), do: ""
def balance_block_number(%Address{fetched_coin_balance_block_number: nil}), do: ""
def balance_block_number(%Address{fetched_balance_block_number: fetched_balance_block_number}) do
to_string(fetched_balance_block_number)
def balance_block_number(%Address{fetched_coin_balance_block_number: fetched_coin_balance_block_number}) do
to_string(fetched_coin_balance_block_number)
end
def contract?(%Address{contract_code: nil}), do: false
@ -37,9 +37,9 @@ defmodule BlockScoutWeb.AddressView do
def contract?(nil), do: true
def formatted_usd(%Address{fetched_balance: nil}, _), do: nil
def formatted_usd(%Address{fetched_coin_balance: nil}, _), do: nil
def formatted_usd(%Address{fetched_balance: balance}, %Token{} = exchange_rate) do
def formatted_usd(%Address{fetched_coin_balance: balance}, %Token{} = exchange_rate) do
case Wei.cast(balance) do
{:ok, wei} ->
wei
@ -79,20 +79,34 @@ defmodule BlockScoutWeb.AddressView do
def smart_contract_with_read_only_functions?(%Address{smart_contract: nil}), do: false
def display_address_hash(current_address, another_address, locale) do
if current_address.hash == another_address.hash do
def display_address_hash(current_address, target_address, locale, truncate \\ false)
def display_address_hash(nil, target_address, locale, truncate) do
render(
"_link.html",
address_hash: target_address.hash,
contract: contract?(target_address),
locale: locale,
truncate: truncate
)
end
def display_address_hash(current_address, target_address, locale, truncate) do
if current_address.hash == target_address.hash do
render(
"_responsive_hash.html",
address_hash: current_address.hash,
contract: contract?(current_address),
locale: locale
locale: locale,
truncate: truncate
)
else
render(
"_link.html",
address_hash: another_address.hash,
contract: contract?(another_address),
locale: locale
address_hash: target_address.hash,
contract: contract?(target_address),
locale: locale,
truncate: truncate
)
end
end

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
alias BlockScoutWeb.API.RPC.RPCView
def render("balance.json", %{addresses: [address]}) do
RPCView.render("show.json", data: "#{address.fetched_balance.value}")
RPCView.render("show.json", data: "#{address.fetched_coin_balance.value}")
end
def render("balance.json", assigns) do
@ -16,7 +16,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
Enum.map(addresses, fn address ->
%{
"account" => "#{address.hash}",
"balance" => "#{address.fetched_balance.value}"
"balance" => "#{address.fetched_coin_balance.value}"
}
end)
@ -38,6 +38,10 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("show.json", data: data)
end
def render("tokenbalance.json", %{token_balance: token_balance}) do
RPCView.render("show.json", data: to_string(token_balance))
end
def render("getminedblocks.json", %{blocks: blocks}) do
data = Enum.map(blocks, &prepare_block/1)
RPCView.render("show.json", data: data)

@ -19,6 +19,12 @@ defmodule BlockScoutWeb.TransactionView do
end
end
def from_or_to_address?(_token_transfer, nil), do: false
def from_or_to_address?(%{from_address_hash: from_hash, to_address_hash: to_hash}, %Address{hash: hash}) do
from_hash == hash || to_hash == hash
end
# This is the address to be shown in the to field
def to_address_hash(%Transaction{to_address_hash: nil, created_contract_address_hash: address_hash}), do: address_hash
@ -45,9 +51,8 @@ defmodule BlockScoutWeb.TransactionView do
AddressView.contract?(from_address) || AddressView.contract?(to_address)
end
def involves_token_transfers?(%Transaction{} = transaction) do
Ecto.assoc_loaded?(transaction.token_transfers) && Enum.any?(transaction.token_transfers)
end
def involves_token_transfers?(%Transaction{token_transfers: []}), do: false
def involves_token_transfers?(%Transaction{token_transfers: transfers}) when is_list(transfers), do: true
def contract_creation?(%Transaction{to_address: nil}), do: true

@ -16,7 +16,7 @@ msgid "Copyright %{year} POA"
msgstr ""
#: lib/block_scout_web/templates/block/_tile.html.eex:39
#: lib/block_scout_web/templates/block/overview.html.eex:87
#: lib/block_scout_web/templates/block/overview.html.eex:84
msgid "Gas Used"
msgstr ""
@ -50,7 +50,7 @@ msgstr ""
msgid "Transactions"
msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction/overview.html.eex:82
msgid "Value"
msgstr ""
@ -63,12 +63,12 @@ msgid "Difficulty"
msgstr ""
#: lib/block_scout_web/templates/block/_tile.html.eex:34
#: lib/block_scout_web/templates/block/overview.html.eex:95
#: lib/block_scout_web/templates/block/overview.html.eex:92
msgid "Gas Limit"
msgstr ""
#: lib/block_scout_web/templates/block/_tile.html.eex:24
#: lib/block_scout_web/templates/block/overview.html.eex:72
#: lib/block_scout_web/templates/block/overview.html.eex:70
#: lib/block_scout_web/templates/chain/_block.html.eex:9
msgid "Miner"
msgstr ""
@ -110,7 +110,7 @@ msgstr ""
msgid "Cumulative Gas Used"
msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:97
#: lib/block_scout_web/templates/transaction/overview.html.eex:93
msgid "Gas"
msgstr ""
@ -137,7 +137,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:106
#: lib/block_scout_web/templates/address_transaction/index.html.eex:117
#: lib/block_scout_web/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:12
#: lib/block_scout_web/views/address_transaction_view.ex:10
msgid "From"
msgstr ""
@ -146,14 +146,14 @@ msgstr ""
msgid "Overview"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:74
#: lib/block_scout_web/views/transaction_view.ex:79
msgid "Success"
msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:94
#: lib/block_scout_web/templates/address_transaction/index.html.eex:105
#: lib/block_scout_web/views/address_internal_transaction_view.ex:9
#: lib/block_scout_web/views/address_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:9
msgid "To"
msgstr ""
@ -198,8 +198,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/index.html.eex:16
#: lib/block_scout_web/templates/transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/overview.html.eex:40
#: lib/block_scout_web/views/transaction_view.ex:38
#: lib/block_scout_web/views/transaction_view.ex:73
#: lib/block_scout_web/views/transaction_view.ex:44
#: lib/block_scout_web/views/transaction_view.ex:78
msgid "Pending"
msgstr ""
@ -267,11 +267,11 @@ msgstr ""
msgid "Next Page"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:71
#: lib/block_scout_web/views/transaction_view.ex:76
msgid "Failed"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:72
#: lib/block_scout_web/views/transaction_view.ex:77
msgid "Out of Gas"
msgstr ""
@ -289,10 +289,9 @@ msgstr ""
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:31
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction/_tile.html.eex:24
#: lib/block_scout_web/templates/transaction/overview.html.eex:82
#: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16
#: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether"
@ -358,7 +357,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88
#: lib/block_scout_web/templates/address_transaction/index.html.eex:99
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:13
#: lib/block_scout_web/views/address_transaction_view.ex:11
msgid "All"
msgstr ""
@ -440,7 +439,7 @@ msgstr ""
msgid "Total Gas Used"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:119
#: lib/block_scout_web/views/transaction_view.ex:124
msgid "Transaction"
msgstr ""
@ -453,9 +452,8 @@ msgstr ""
msgid "View All"
msgstr ""
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:34
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:69
#: lib/block_scout_web/templates/transaction/_tile.html.eex:22
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27
#: lib/block_scout_web/templates/transaction/overview.html.eex:57
msgid "TX Fee"
msgstr ""
@ -479,7 +477,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:132
#: lib/block_scout_web/templates/address_transaction/index.html.eex:145
#: lib/block_scout_web/templates/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:51
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:78
#: lib/block_scout_web/templates/tokens/token/show.html.eex:71
#: lib/block_scout_web/templates/transaction/index.html.eex:66
@ -520,13 +518,13 @@ msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:78
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:77
#: lib/block_scout_web/templates/transaction_log/index.html.eex:122
msgid "Newer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:117
#: lib/block_scout_web/views/transaction_view.ex:122
msgid "Contract Creation"
msgstr ""
@ -623,7 +621,7 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:118
#: lib/block_scout_web/views/transaction_view.ex:123
msgid "Contract Call"
msgstr ""
@ -638,25 +636,22 @@ msgid "at"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:41
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:38
#: lib/block_scout_web/templates/transaction/_tile.html.eex:28
#: lib/block_scout_web/templates/transaction/_tile.html.eex:34
msgid "Block #%{number}"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:29
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:54
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:92
#: lib/block_scout_web/templates/transaction/_tile.html.eex:47
msgid "IN"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:27
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:50
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:88
#: lib/block_scout_web/templates/transaction/_tile.html.eex:43
msgid "OUT"
msgstr ""
@ -713,7 +708,7 @@ msgid "Block Confirmations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:109
#: lib/block_scout_web/templates/transaction/overview.html.eex:105
msgid "Limit"
msgstr ""
@ -729,19 +724,18 @@ msgid "There are no logs for this transaction."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:102
#: lib/block_scout_web/templates/transaction/overview.html.eex:98
msgid "Used"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:116
#: lib/block_scout_web/views/transaction_view.ex:121
msgid "Token Transfer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:64
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:18
msgid "Transfers"
msgstr ""
@ -1001,3 +995,13 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57
msgid "loading....."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:67
msgid "View More Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:68
msgid "View Less Transfers"
msgstr ""

@ -28,7 +28,7 @@ msgid "Copyright %{year} POA"
msgstr "%{year} POA Network Ltd. All rights reserved"
#: lib/block_scout_web/templates/block/_tile.html.eex:39
#: lib/block_scout_web/templates/block/overview.html.eex:87
#: lib/block_scout_web/templates/block/overview.html.eex:84
msgid "Gas Used"
msgstr "Gas Used"
@ -62,7 +62,7 @@ msgstr "BlockScout"
msgid "Transactions"
msgstr "Transactions"
#: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction/overview.html.eex:82
msgid "Value"
msgstr "Value"
@ -75,12 +75,12 @@ msgid "Difficulty"
msgstr "Difficulty"
#: lib/block_scout_web/templates/block/_tile.html.eex:34
#: lib/block_scout_web/templates/block/overview.html.eex:95
#: lib/block_scout_web/templates/block/overview.html.eex:92
msgid "Gas Limit"
msgstr "Gas Limit"
#: lib/block_scout_web/templates/block/_tile.html.eex:24
#: lib/block_scout_web/templates/block/overview.html.eex:72
#: lib/block_scout_web/templates/block/overview.html.eex:70
#: lib/block_scout_web/templates/chain/_block.html.eex:9
msgid "Miner"
msgstr "Validator"
@ -122,7 +122,7 @@ msgstr "Transaction Details"
msgid "Cumulative Gas Used"
msgstr "Cumulative Gas Used"
#: lib/block_scout_web/templates/transaction/overview.html.eex:97
#: lib/block_scout_web/templates/transaction/overview.html.eex:93
msgid "Gas"
msgstr "Gas"
@ -149,7 +149,7 @@ msgstr "Address"
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:106
#: lib/block_scout_web/templates/address_transaction/index.html.eex:117
#: lib/block_scout_web/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:12
#: lib/block_scout_web/views/address_transaction_view.ex:10
msgid "From"
msgstr "From"
@ -158,14 +158,14 @@ msgstr "From"
msgid "Overview"
msgstr "Overview"
#: lib/block_scout_web/views/transaction_view.ex:74
#: lib/block_scout_web/views/transaction_view.ex:79
msgid "Success"
msgstr "Success"
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:94
#: lib/block_scout_web/templates/address_transaction/index.html.eex:105
#: lib/block_scout_web/views/address_internal_transaction_view.ex:9
#: lib/block_scout_web/views/address_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:9
msgid "To"
msgstr "To"
@ -210,8 +210,8 @@ msgstr "Showing %{count} Transactions"
#: lib/block_scout_web/templates/transaction/index.html.eex:16
#: lib/block_scout_web/templates/transaction/index.html.eex:35
#: lib/block_scout_web/templates/transaction/overview.html.eex:40
#: lib/block_scout_web/views/transaction_view.ex:38
#: lib/block_scout_web/views/transaction_view.ex:73
#: lib/block_scout_web/views/transaction_view.ex:44
#: lib/block_scout_web/views/transaction_view.ex:78
msgid "Pending"
msgstr "Pending"
@ -279,11 +279,11 @@ msgstr ""
msgid "Next Page"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:71
#: lib/block_scout_web/views/transaction_view.ex:76
msgid "Failed"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:72
#: lib/block_scout_web/views/transaction_view.ex:77
msgid "Out of Gas"
msgstr ""
@ -301,10 +301,9 @@ msgstr ""
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:22
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:31
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:68
#: lib/block_scout_web/templates/transaction/_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/overview.html.eex:84
#: lib/block_scout_web/templates/transaction/_tile.html.eex:24
#: lib/block_scout_web/templates/transaction/overview.html.eex:82
#: lib/block_scout_web/templates/transaction_internal_transaction/_internal_transaction.html.eex:16
#: lib/block_scout_web/views/wei_helpers.ex:72
msgid "Ether"
@ -370,7 +369,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88
#: lib/block_scout_web/templates/address_transaction/index.html.eex:99
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:13
#: lib/block_scout_web/views/address_transaction_view.ex:11
msgid "All"
msgstr ""
@ -452,7 +451,7 @@ msgstr ""
msgid "Total Gas Used"
msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:119
#: lib/block_scout_web/views/transaction_view.ex:124
msgid "Transaction"
msgstr ""
@ -465,9 +464,8 @@ msgstr ""
msgid "View All"
msgstr ""
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:34
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:69
#: lib/block_scout_web/templates/transaction/_tile.html.eex:22
#: lib/block_scout_web/templates/transaction/_tile.html.eex:27
#: lib/block_scout_web/templates/transaction/overview.html.eex:57
msgid "TX Fee"
msgstr ""
@ -491,7 +489,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:132
#: lib/block_scout_web/templates/address_transaction/index.html.eex:145
#: lib/block_scout_web/templates/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:51
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
#: lib/block_scout_web/templates/pending_transaction/index.html.eex:78
#: lib/block_scout_web/templates/tokens/token/show.html.eex:71
#: lib/block_scout_web/templates/transaction/index.html.eex:66
@ -532,13 +530,13 @@ msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:78
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:77
#: lib/block_scout_web/templates/transaction_log/index.html.eex:122
msgid "Newer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:117
#: lib/block_scout_web/views/transaction_view.ex:122
msgid "Contract Creation"
msgstr ""
@ -635,7 +633,7 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:118
#: lib/block_scout_web/views/transaction_view.ex:123
msgid "Contract Call"
msgstr ""
@ -650,25 +648,22 @@ msgid "at"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:41
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:38
#: lib/block_scout_web/templates/transaction/_tile.html.eex:28
#: lib/block_scout_web/templates/transaction/_tile.html.eex:34
msgid "Block #%{number}"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:29
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:54
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:92
#: lib/block_scout_web/templates/transaction/_tile.html.eex:47
msgid "IN"
msgstr ""
#, elixir-format
#:
#: lib/block_scout_web/templates/address_internal_transaction/_internal_transaction.html.eex:27
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:50
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:88
#: lib/block_scout_web/templates/transaction/_tile.html.eex:43
msgid "OUT"
msgstr ""
@ -725,7 +720,7 @@ msgid "Block Confirmations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:109
#: lib/block_scout_web/templates/transaction/overview.html.eex:105
msgid "Limit"
msgstr ""
@ -741,19 +736,18 @@ msgid "There are no logs for this transaction."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:102
#: lib/block_scout_web/templates/transaction/overview.html.eex:98
msgid "Used"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/token/_token_transfer.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:116
#: lib/block_scout_web/views/transaction_view.ex:121
msgid "Token Transfer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/_transaction.html.eex:64
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:18
msgid "Transfers"
msgstr ""
@ -1013,3 +1007,13 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:57
msgid "loading....."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:67
msgid "View More Transfers"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tile.html.eex:68
msgid "View Less Transfers"
msgstr ""

@ -29,7 +29,7 @@ defmodule BlockScoutWeb.AddressChannelTest do
end
test "notified of balance_update for matching address", %{address: address, topic: topic} do
address_with_balance = %{address | fetched_balance: 1}
address_with_balance = %{address | fetched_coin_balance: 1}
Notifier.handle_event({:chain_event, :addresses, [address_with_balance]})
receive do
@ -41,11 +41,11 @@ defmodule BlockScoutWeb.AddressChannelTest do
end
end
test "not notified of balance_update if fetched_balance is nil", %{address: address} do
test "not notified of balance_update if fetched_coin_balance is nil", %{address: address} do
Notifier.handle_event({:chain_event, :addresses, [address]})
receive do
_ -> assert false, "Message was broadcast for nil fetched_balance."
_ -> assert false, "Message was broadcast for nil fetched_coin_balance."
after
100 -> assert true
end

@ -59,7 +59,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
test "with a valid address", %{conn: conn} do
address = insert(:address, fetched_balance: 100)
address = insert(:address, fetched_coin_balance: 100)
params = %{
"module" => "account",
@ -72,7 +72,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> get("/api", params)
|> json_response(200)
assert response["result"] == "#{address.fetched_balance.value}"
assert response["result"] == "#{address.fetched_coin_balance.value}"
assert response["status"] == "1"
assert response["message"] == "OK"
end
@ -80,7 +80,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
test "with multiple valid addresses", %{conn: conn} do
addresses =
for _ <- 1..2 do
insert(:address, fetched_balance: Enum.random(1..1_000))
insert(:address, fetched_coin_balance: Enum.random(1..1_000))
end
address_param =
@ -96,7 +96,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
expected_result =
Enum.map(addresses, fn address ->
%{"account" => "#{address.hash}", "balance" => "#{address.fetched_balance.value}"}
%{"account" => "#{address.hash}", "balance" => "#{address.fetched_coin_balance.value}"}
end)
assert response =
@ -110,7 +110,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
test "supports GET and POST requests", %{conn: conn} do
address = insert(:address, fetched_balance: 100)
address = insert(:address, fetched_coin_balance: 100)
params = %{
"module" => "account",
@ -182,7 +182,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
test "with multiple valid addresses", %{conn: conn} do
addresses =
for _ <- 1..4 do
insert(:address, fetched_balance: Enum.random(1..1_000))
insert(:address, fetched_coin_balance: Enum.random(1..1_000))
end
address_param =
@ -198,7 +198,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
expected_result =
Enum.map(addresses, fn address ->
%{"account" => "#{address.hash}", "balance" => "#{address.fetched_balance.value}"}
%{"account" => "#{address.hash}", "balance" => "#{address.fetched_coin_balance.value}"}
end)
assert response =
@ -212,7 +212,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
test "with an address that exists and one that doesn't", %{conn: conn} do
address1 = insert(:address, fetched_balance: 100)
address1 = insert(:address, fetched_coin_balance: 100)
address2_hash = "0x9bf49d5875030175f3d5d4a67631a87ab4df526b"
params = %{
@ -223,7 +223,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
expected_result = [
%{"account" => address2_hash, "balance" => "0"},
%{"account" => "#{address1.hash}", "balance" => "#{address1.fetched_balance.value}"}
%{"account" => "#{address1.hash}", "balance" => "#{address1.fetched_coin_balance.value}"}
]
assert response =
@ -237,7 +237,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
test "up to a maximum of 20 addresses in a single request", %{conn: conn} do
addresses = insert_list(25, :address, fetched_balance: 0)
addresses = insert_list(25, :address, fetched_coin_balance: 0)
address_param =
addresses
@ -261,7 +261,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
test "with a single address", %{conn: conn} do
address = insert(:address, fetched_balance: 100)
address = insert(:address, fetched_coin_balance: 100)
params = %{
"module" => "account",
@ -270,7 +270,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
}
expected_result = [
%{"account" => "#{address.hash}", "balance" => "#{address.fetched_balance.value}"}
%{"account" => "#{address.hash}", "balance" => "#{address.fetched_coin_balance.value}"}
]
assert response =
@ -286,7 +286,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
test "supports GET and POST requests", %{conn: conn} do
addresses =
for _ <- 1..4 do
insert(:address, fetched_balance: Enum.random(1..1_000))
insert(:address, fetched_coin_balance: Enum.random(1..1_000))
end
address_param =
@ -1331,6 +1331,158 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
end
end
describe "tokenbalance" do
test "without required params", %{conn: conn} do
params = %{
"module" => "account",
"action" => "tokenbalance"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "missing: address, contractaddress"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with contractaddress but without address", %{conn: conn} do
params = %{
"module" => "account",
"action" => "tokenbalance",
"contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "missing: address"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with address but without contractaddress", %{conn: conn} do
params = %{
"module" => "account",
"action" => "tokenbalance",
"address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "missing: contractaddress"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with an invalid contractaddress hash", %{conn: conn} do
params = %{
"module" => "account",
"action" => "tokenbalance",
"contractaddress" => "badhash",
"address" => "badhash"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "Invalid contractaddress format"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with an invalid address hash", %{conn: conn} do
params = %{
"module" => "account",
"action" => "tokenbalance",
"contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
"address" => "badhash"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "Invalid address format"
assert response["status"] == "0"
assert Map.has_key?(response, "result")
refute response["result"]
end
test "with a contractaddress and address that doesn't exist", %{conn: conn} do
params = %{
"module" => "account",
"action" => "tokenbalance",
"contractaddress" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
"address" => "0x9bf38d4764929064f2d4d3a56520a76ab3df415b"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == "0"
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with contractaddress and address without row in token_balances table", %{conn: conn} do
token = insert(:token)
address = insert(:address)
params = %{
"module" => "account",
"action" => "tokenbalance",
"contractaddress" => to_string(token.contract_address_hash),
"address" => to_string(address.hash)
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == "0"
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with contractaddress and address with existing balance in token_balances table", %{conn: conn} do
token_balance = insert(:token_balance)
params = %{
"module" => "account",
"action" => "tokenbalance",
"contractaddress" => to_string(token_balance.token_contract_address_hash),
"address" => to_string(token_balance.address_hash)
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == to_string(token_balance.value)
assert response["status"] == "1"
assert response["message"] == "OK"
end
end
describe "getminedblocks" do
test "with missing address hash", %{conn: conn} do
params = %{
@ -1613,4 +1765,26 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert AddressController.optional_params(params) == %{}
end
end
describe "fetch_required_params/2" do
test "returns error with missing param" do
params = %{"address" => "some address"}
required_params = ~w(address contractaddress)
result = AddressController.fetch_required_params(params, required_params)
assert result == {:required_params, {:error, ["contractaddress"]}}
end
test "returns ok with all required params" do
params = %{"address" => "some address", "contractaddress" => "some contract"}
required_params = ~w(address contractaddress)
result = AddressController.fetch_required_params(params, required_params)
assert result == {:required_params, {:ok, params}}
end
end
end

@ -36,11 +36,11 @@ defmodule BlockScoutWeb.AddressPage do
end
def internal_transaction_address_link(%InternalTransaction{id: id, to_address_hash: address_hash}, :to) do
css("[data-internal-transaction-id='#{id}'] [data-address-hash='#{address_hash}'][data-test='address_hash_link']")
css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end
def internal_transaction_address_link(%InternalTransaction{id: id, from_address_hash: address_hash}, :from) do
css("[data-internal-transaction-id='#{id}'] [data-address-hash='#{address_hash}'][data-test='address_hash_link']")
css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end
def transaction(%Transaction{hash: transaction_hash}), do: transaction(transaction_hash)
@ -56,11 +56,11 @@ defmodule BlockScoutWeb.AddressPage do
end
def transaction_address_link(%Transaction{hash: hash, to_address_hash: address_hash}, :to) do
css("[data-transaction-hash='#{hash}'] [data-address-hash='#{address_hash}'][data-test='address_hash_link']")
css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end
def transaction_address_link(%Transaction{hash: hash, from_address_hash: address_hash}, :from) do
css("[data-transaction-hash='#{hash}'] [data-address-hash='#{address_hash}'][data-test='address_hash_link']")
css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end
def transaction_count do
@ -77,12 +77,19 @@ defmodule BlockScoutWeb.AddressPage do
visit(session, "/en/addresses/#{address_hash}")
end
def token_transfers(count: count) do
css("[data-test='token_transfer']", count: count)
def token_transfer(%Transaction{hash: transaction_hash}, %Address{hash: address_hash}, count: count) do
css(
"[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfer'] [data-address-hash='#{address_hash}']",
count: count
)
end
def token_transfer(address_hash, count: count) do
css("[data-test='token_transfer_address_hash']", count: count, text: to_string(address_hash))
def token_transfers(%Transaction{hash: transaction_hash}, count: count) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfer']", count: count)
end
def token_transfers_expansion(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']")
end
def transaction_type do

@ -15,6 +15,14 @@ defmodule BlockScoutWeb.BlockPage do
css("[data-test='block_detail_number']", text: to_string(block_number))
end
def token_transfers(%Transaction{hash: transaction_hash}, count: count) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfer']", count: count)
end
def token_transfers_expansion(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']")
end
def transaction(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}']")
end

@ -30,6 +30,14 @@ defmodule BlockScoutWeb.ChainPage do
|> send_keys([:enter])
end
def token_transfers(%Transaction{hash: transaction_hash}, count: count) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfer']", count: count)
end
def token_transfers_expansion(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']")
end
def transactions(count: count) do
css("[data-test='chain_transaction']", count: count)
end

@ -8,7 +8,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
block = insert(:block)
{:ok, balance} = Wei.cast(5)
lincoln = insert(:address, fetched_balance: balance)
lincoln = insert(:address, fetched_coin_balance: balance)
taft = insert(:address)
from_taft =
@ -30,7 +30,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
end
test "viewing address overview information", %{session: session} do
address = insert(:address, fetched_balance: 500)
address = insert(:address, fetched_coin_balance: 500)
session
|> AddressPage.visit_page(address)
@ -261,12 +261,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address =
insert(
:address,
contract_code: Explorer.Factory.data("contract_code")
)
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
transaction =
@ -284,9 +279,10 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
session
|> AddressPage.visit_page(lincoln)
|> assert_has(AddressPage.token_transfers(count: 1))
|> assert_has(AddressPage.token_transfer(lincoln.hash, count: 1))
|> assert_has(AddressPage.token_transfer(taft.hash, count: 1))
|> 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
test "contributor can see only token transfers related to him", %{
@ -298,6 +294,46 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
taft = addresses.taft
morty = build(:address)
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> with_block(block)
insert(
:token_transfer,
from_address: lincoln,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
)
insert(
:token_transfer,
from_address: lincoln,
to_address: morty,
transaction: transaction,
token_contract_address: contract_token_address
)
session
|> AddressPage.visit_page(morty)
|> assert_has(AddressPage.token_transfers(transaction, count: 1))
|> assert_has(AddressPage.token_transfer(transaction, lincoln, count: 1))
|> assert_has(AddressPage.token_transfer(transaction, morty, count: 1))
|> refute_has(AddressPage.token_transfer(transaction, taft, count: 1))
end
test "transactions with multiple token transfers shows only the first one by default", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address =
insert(
:address,
@ -311,7 +347,8 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> insert(from_address: lincoln, to_address: contract_token_address)
|> with_block(block)
insert(
insert_list(
3,
:token_transfer,
from_address: lincoln,
to_address: taft,
@ -319,20 +356,40 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
token_contract_address: contract_token_address
)
insert(
session
|> AddressPage.visit_page(lincoln)
|> assert_has(AddressPage.token_transfers(transaction, count: 1))
end
test "transaction with multiple token transfers shows all transfers if expanded", %{
addresses: addresses,
block: block,
session: session
} do
lincoln = addresses.lincoln
taft = addresses.taft
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
transaction =
:transaction
|> insert(from_address: lincoln, to_address: contract_token_address)
|> with_block(block)
insert_list(
3,
:token_transfer,
from_address: lincoln,
to_address: morty,
to_address: taft,
transaction: transaction,
token_contract_address: contract_token_address
)
session
|> AddressPage.visit_page(morty)
|> assert_has(AddressPage.token_transfers(count: 1))
|> assert_has(AddressPage.token_transfer(lincoln.hash, count: 1))
|> assert_has(AddressPage.token_transfer(morty.hash, count: 1))
|> refute_has(AddressPage.token_transfer(taft.hash, count: 1))
|> AddressPage.visit_page(lincoln)
|> click(AddressPage.token_transfers_expansion(transaction))
|> assert_has(AddressPage.token_transfers(transaction, count: 3))
end
end
end

@ -20,53 +20,81 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
{:ok, first_shown_block: newest_block, last_shown_block: oldest_block}
end
test "show block detail page", %{session: session} do
block = insert(:block, number: 42)
test "viewing the blocks index page", %{first_shown_block: block, session: session} do
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.detail_number(block))
|> BlockListPage.visit_page()
|> assert_has(BlockListPage.block(block))
end
test "block detail page has transactions", %{session: session} do
block = insert(:block, number: 42)
describe "block details page" do
test "show block detail page", %{session: session} do
block = insert(:block, number: 42)
transaction =
:transaction
|> insert()
|> with_block(block)
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.detail_number(block))
end
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.detail_number(block))
|> assert_has(BlockPage.transaction(transaction))
|> assert_has(BlockPage.transaction_status(transaction))
end
test "block detail page has transactions", %{session: session} do
block = insert(:block, number: 42)
test "contract creation is shown for to_address in transaction list", %{session: session} do
block = insert(:block, number: 42)
transaction =
:transaction
|> insert()
|> with_block(block)
contract_address = insert(:contract_address)
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.detail_number(block))
|> assert_has(BlockPage.transaction(transaction))
|> assert_has(BlockPage.transaction_status(transaction))
end
transaction =
:transaction
|> insert(to_address: nil)
|> with_contract_creation(contract_address)
|> with_block(block)
test "contract creation is shown for to_address in transaction list", %{session: session} do
block = insert(:block, number: 42)
internal_transaction =
:internal_transaction_create
|> insert(transaction: transaction, index: 0)
|> with_contract_creation(contract_address)
contract_address = insert(:contract_address)
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.contract_creation(internal_transaction))
end
transaction =
:transaction
|> insert(to_address: nil)
|> with_contract_creation(contract_address)
|> with_block(block)
test "viewing the blocks index page", %{first_shown_block: block, session: session} do
session
|> BlockListPage.visit_page()
|> assert_has(BlockListPage.block(block))
internal_transaction =
:internal_transaction_create
|> insert(transaction: transaction, index: 0)
|> with_contract_creation(contract_address)
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.contract_creation(internal_transaction))
end
test "transaction with multiple token transfers shows all transfers if expanded", %{
first_shown_block: block,
session: session
} do
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
transaction =
:transaction
|> insert(to_address: contract_token_address)
|> with_block(block)
insert_list(
3,
:token_transfer,
transaction: transaction,
token_contract_address: contract_token_address
)
session
|> BlockPage.visit_page(block)
|> assert_has(BlockPage.token_transfers(transaction, count: 1))
|> click(BlockPage.token_transfers_expansion(transaction))
|> assert_has(BlockPage.token_transfers(transaction, count: 3))
end
end
end

@ -81,5 +81,31 @@ defmodule BlockScoutWeb.ViewingChainTest do
|> ChainPage.visit_page()
|> assert_has(ChainPage.contract_creation(transaction))
end
test "transaction with multiple token transfers shows all transfers if expanded", %{
block: block,
session: session
} do
contract_token_address = insert(:contract_address)
insert(:token, contract_address: contract_token_address)
transaction =
:transaction
|> insert(to_address: contract_token_address)
|> with_block(block)
insert_list(
3,
:token_transfer,
transaction: transaction,
token_contract_address: contract_token_address
)
session
|> ChainPage.visit_page()
|> assert_has(ChainPage.token_transfers(transaction, count: 1))
|> click(ChainPage.token_transfers_expansion(transaction))
|> assert_has(ChainPage.token_transfers(transaction, count: 3))
end
end
end

@ -19,8 +19,8 @@ defmodule BlockScoutWeb.AddressViewTest do
end
describe "formatted_usd/2" do
test "without a fetched_balance returns nil" do
address = build(:address, fetched_balance: nil)
test "without a fetched_coin_balance returns nil" do
address = build(:address, fetched_coin_balance: nil)
token = %Token{usd_value: Decimal.new(0.5)}
assert nil == AddressView.formatted_usd(address, token)
end
@ -32,7 +32,7 @@ defmodule BlockScoutWeb.AddressViewTest do
end
test "returns formatted usd value" do
address = build(:address, fetched_balance: 10_000_000_000_000)
address = build(:address, fetched_coin_balance: 10_000_000_000_000)
token = %Token{usd_value: Decimal.new(0.5)}
assert "$0.000005 USD" == AddressView.formatted_usd(address, token)
end

@ -21,7 +21,7 @@ defmodule Explorer.Chain do
alias Explorer.Chain.{
Address,
Address.TokenBalance,
Balance,
Address.CoinBalance,
Block,
Data,
Hash,
@ -221,7 +221,7 @@ defmodule Explorer.Chain do
@spec balance(Address.t(), :wei) :: Wei.wei() | nil
@spec balance(Address.t(), :gwei) :: Wei.gwei() | nil
@spec balance(Address.t(), :ether) :: Wei.ether() | nil
def balance(%Address{fetched_balance: balance}, unit) do
def balance(%Address{fetched_coin_balance: balance}, unit) do
case balance do
nil -> nil
_ -> Wei.to(balance, unit)
@ -324,6 +324,7 @@ defmodule Explorer.Chain do
|> join(:inner, [transaction], block in assoc(transaction, :block))
|> where([_, block], block.hash == ^block_hash)
|> join_associations(necessity_by_association)
|> preload([{:token_transfers, [:token, :from_address, :to_address]}])
|> Repo.all()
end
@ -641,6 +642,7 @@ defmodule Explorer.Chain do
fetch_transactions()
|> where([transaction], transaction.hash in ^hashes)
|> join_associations(necessity_by_association)
|> preload([{:token_transfers, [:token, :from_address, :to_address]}])
|> Repo.all()
end
@ -727,7 +729,7 @@ defmodule Explorer.Chain do
end
@doc """
Returns a stream of unfetched `t:Explorer.Chain.Balance.t/0`.
Returns a stream of unfetched `t:Explorer.Chain.Address.CoinBalance.t/0`.
When there are addresses, the `reducer` is called for each `t:Explorer.Chain.Address.t/0` `hash` and all
`t:Explorer.Chain.Block.t/0` `block_number` that address is mentioned.
@ -761,7 +763,7 @@ defmodule Explorer.Chain do
fn ->
query =
from(
balance in Balance,
balance in CoinBalance,
where: is_nil(balance.value_fetched_at),
select: %{address_hash: balance.address_hash, block_number: balance.block_number}
)
@ -1070,6 +1072,7 @@ defmodule Explorer.Chain do
|> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index))
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
|> join_associations(necessity_by_association)
|> preload([{:token_transfers, [:token, :from_address, :to_address]}])
|> Repo.all()
end
@ -1108,6 +1111,7 @@ defmodule Explorer.Chain do
|> where([transaction], is_nil(transaction.block_hash))
|> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash)
|> join_associations(necessity_by_association)
|> preload([{:token_transfers, [:token, :from_address, :to_address]}])
|> Repo.all()
end

@ -8,7 +8,7 @@ defmodule Explorer.Chain.Address do
alias Ecto.Changeset
alias Explorer.Chain.{Address, Block, Data, Hash, Wei, SmartContract, InternalTransaction, Token}
@optional_attrs ~w(contract_code fetched_balance fetched_balance_block_number)a
@optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number)a
@required_attrs ~w(hash)a
@allowed_attrs @optional_attrs ++ @required_attrs
@ -18,9 +18,9 @@ defmodule Explorer.Chain.Address do
@type hash :: Hash.t()
@typedoc """
* `fetched_balance` - The last fetched balance from Parity
* `fetched_balance_block_number` - the `t:Explorer.Chain.Block.t/0` `t:Explorer.Chain.Block.block_number/0` for
which `fetched_balance` was fetched
* `fetched_coin_balance` - The last fetched balance from Parity
* `fetched_coin_balance_block_number` - the `t:Explorer.Chain.Block.t/0` `t:Explorer.Chain.Block.block_number/0` for
which `fetched_coin_balance` was fetched
* `hash` - the hash of the address's public key
* `contract_code` - the code of the contract when an Address is a contract
* `names` - names known for the address
@ -28,8 +28,8 @@ defmodule Explorer.Chain.Address do
* `updated_at` when this address was last updated
"""
@type t :: %__MODULE__{
fetched_balance: Wei.t(),
fetched_balance_block_number: Block.block_number(),
fetched_coin_balance: Wei.t(),
fetched_coin_balance_block_number: Block.block_number(),
hash: Hash.Address.t(),
contract_code: Data.t() | nil,
names: %Ecto.Association.NotLoaded{} | [Address.Name.t()],
@ -39,8 +39,8 @@ defmodule Explorer.Chain.Address do
@primary_key {:hash, Hash.Address, autogenerate: false}
schema "addresses" do
field(:fetched_balance, Wei)
field(:fetched_balance_block_number, :integer)
field(:fetched_coin_balance, Wei)
field(:fetched_coin_balance_block_number, :integer)
field(:contract_code, Data)
has_one(:smart_contract, SmartContract)
@ -57,7 +57,7 @@ defmodule Explorer.Chain.Address do
timestamps()
end
@balance_changeset_required_attrs @required_attrs ++ ~w(fetched_balance fetched_balance_block_number)a
@balance_changeset_required_attrs @required_attrs ++ ~w(fetched_coin_balance fetched_coin_balance_block_number)a
def balance_changeset(%__MODULE__{} = address, attrs) do
address

@ -1,4 +1,4 @@
defmodule Explorer.Chain.Balance do
defmodule Explorer.Chain.Address.CoinBalance do
@moduledoc """
The `t:Explorer.Chain.Wei.t/0` `value` of `t:Explorer.Chain.Address.t/0` at the end of a `t:Explorer.Chain.Block.t/0`
`t:Explorer.Chain.Block.block_number/0`.
@ -17,12 +17,12 @@ defmodule Explorer.Chain.Balance do
* `address_hash` - foreign key for `address`.
* `block_number` - the `t:Explorer.Chain.Block.block_number/0` for the `t:Explorer.Chain.Block.t/0` at the end of
which `address` had `value`. When `block_number` is the greatest `t:Explorer.Chain.Block.block_number/0` for a
given `address`, the `t:Explorer.Chain.Address.t/0` `fetched_balance_block_number` will match this value.
given `address`, the `t:Explorer.Chain.Address.t/0` `fetched_coin_balance_block_number` will match this value.
* `inserted_at` - When the balance was first inserted into the database.
* `updated_at` - When the balance was last updated.
* `value` - the value of `address` at the end of the `t:Explorer.Chain.Block.block_number/0` for the
`t:Explorer.Chain.Block.t/0`. When `block_number` is the greatest `t:Explorer.Chain.Block.block_number/0` for a
given `address`, the `t:Explorer.Chain.Address.t/0` `fetched_balance` will match this value.
given `address`, the `t:Explorer.Chain.Address.t/0` `fetched_coin_balance` will match this value.
* `value_fetched_at` - when `value` was fetched.
"""
@type t :: %__MODULE__{
@ -35,7 +35,7 @@ defmodule Explorer.Chain.Balance do
}
@primary_key false
schema "balances" do
schema "address_coin_balances" do
field(:block_number, :integer)
field(:value, Wei)
field(:value_fetched_at, :utc_datetime)
@ -50,6 +50,6 @@ defmodule Explorer.Chain.Balance do
|> cast(params, @allowed_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:address_hash)
|> unique_constraint(:block_number, name: :balances_address_hash_block_number_index)
|> unique_constraint(:block_number, name: :address_coin_balances_address_hash_block_number_index)
end
end

@ -9,8 +9,8 @@ defmodule Explorer.Chain.Import do
alias Explorer.Chain.{
Address,
Address.CoinBalance,
Address.TokenBalance,
Balance,
Block,
Hash,
InternalTransaction,
@ -160,7 +160,7 @@ defmodule Explorer.Chain.Import do
* `:timeout` - the timeout for inserting all addresses. Defaults to `#{@insert_addresses_timeout}` milliseconds.
* `:with` - the changeset function on `Explorer.Chain.Address` to use validate `:params`.
* `:balances`
* `:params` - `list` of params for `Explorer.Chain.Balance.changeset/2`.
* `:params` - `list` of params for `Explorer.Chain.Address.CoinBalance.changeset/2`.
* `:timeout` - the timeout for inserting all balances. Defaults to `#{@insert_balances_timeout}` milliseconds.
* `:blocks`
* `:params` - `list` of params for `Explorer.Chain.Block.changeset/2`.
@ -271,7 +271,7 @@ defmodule Explorer.Chain.Import do
@import_option_key_to_ecto_schema_module %{
addresses: Address,
balances: Balance,
balances: CoinBalance,
blocks: Block,
internal_transactions: InternalTransaction,
logs: Log,
@ -322,7 +322,7 @@ defmodule Explorer.Chain.Import do
defp run_balances(multi, ecto_schema_module_to_changes_list_map, options)
when is_map(ecto_schema_module_to_changes_list_map) and is_map(options) do
case ecto_schema_module_to_changes_list_map do
%{Balance => balances_changes} ->
%{CoinBalance => balances_changes} ->
timestamps = Map.fetch!(options, :timestamps)
Multi.run(multi, :balances, fn _ ->
@ -521,34 +521,34 @@ defmodule Explorer.Chain.Import do
set: [
contract_code: fragment("COALESCE(?, EXCLUDED.contract_code)", address.contract_code),
# ARGMAX on two columns
fetched_balance:
fetched_coin_balance:
fragment(
"""
CASE WHEN EXCLUDED.fetched_balance_block_number IS NOT NULL AND
CASE WHEN EXCLUDED.fetched_coin_balance_block_number IS NOT NULL AND
(? IS NULL OR
EXCLUDED.fetched_balance_block_number >= ?) THEN
EXCLUDED.fetched_balance
EXCLUDED.fetched_coin_balance_block_number >= ?) THEN
EXCLUDED.fetched_coin_balance
ELSE ?
END
""",
address.fetched_balance_block_number,
address.fetched_balance_block_number,
address.fetched_balance
address.fetched_coin_balance_block_number,
address.fetched_coin_balance_block_number,
address.fetched_coin_balance
),
# MAX on two columns
fetched_balance_block_number:
fetched_coin_balance_block_number:
fragment(
"""
CASE WHEN EXCLUDED.fetched_balance_block_number IS NOT NULL AND
CASE WHEN EXCLUDED.fetched_coin_balance_block_number IS NOT NULL AND
(? IS NULL OR
EXCLUDED.fetched_balance_block_number >= ?) THEN
EXCLUDED.fetched_balance_block_number
EXCLUDED.fetched_coin_balance_block_number >= ?) THEN
EXCLUDED.fetched_coin_balance_block_number
ELSE ?
END
""",
address.fetched_balance_block_number,
address.fetched_balance_block_number,
address.fetched_balance_block_number
address.fetched_coin_balance_block_number,
address.fetched_coin_balance_block_number,
address.fetched_coin_balance_block_number
)
]
]
@ -589,7 +589,7 @@ defmodule Explorer.Chain.Import do
conflict_target: [:address_hash, :block_number],
on_conflict:
from(
balance in Balance,
balance in CoinBalance,
update: [
set: [
inserted_at: fragment("LEAST(EXCLUDED.inserted_at, ?)", balance.inserted_at),
@ -623,7 +623,7 @@ defmodule Explorer.Chain.Import do
]
]
),
for: Balance,
for: CoinBalance,
timeout: timeout,
timestamps: timestamps
)

@ -9,6 +9,7 @@ defmodule Explorer.Etherscan do
alias Explorer.{Repo, Chain}
alias Explorer.Chain.{Block, Hash, InternalTransaction, Transaction, Wei}
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.Address.TokenBalance
@default_options %{
order_by_direction: :asc,
@ -160,6 +161,27 @@ defmodule Explorer.Etherscan do
Repo.all(query)
end
@doc """
Gets the token balance for a given contract address hash and address hash.
"""
@spec get_token_balance(Hash.Address.t(), Hash.Address.t()) :: TokenBalance.t() | nil
def get_token_balance(
%Hash{byte_count: unquote(Hash.Address.byte_count())} = contract_address_hash,
%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash
) do
query =
from(tb in TokenBalance,
where: tb.token_contract_address_hash == ^contract_address_hash,
where: tb.address_hash == ^address_hash,
order_by: [desc: :block_number],
limit: 1,
select: tb
)
Repo.one(query)
end
@transaction_fields ~w(
block_hash
block_number

@ -3,8 +3,8 @@ defmodule Explorer.Repo.Migrations.CreateAddress do
def change do
create table(:addresses, primary_key: false) do
add(:fetched_balance, :numeric, precision: 100)
add(:fetched_balance_block_number, :bigint)
add(:fetched_coin_balance, :numeric, precision: 100)
add(:fetched_coin_balance_block_number, :bigint)
add(:hash, :bytea, null: false, primary_key: true)
add(:contract_code, :bytea, null: true)

@ -1,8 +1,8 @@
defmodule Explorer.Repo.Migrations.CreateBalances do
defmodule Explorer.Repo.Migrations.CreateCoinBalances do
use Ecto.Migration
def change do
create table(:balances, primary_key: false) do
create table(:address_coin_balances, primary_key: false) do
add(:address_hash, references(:addresses, column: :hash, type: :bytea), null: false)
add(:block_number, :bigint, null: false)
@ -13,11 +13,11 @@ defmodule Explorer.Repo.Migrations.CreateBalances do
timestamps(null: false, type: :utc_datetime)
end
create(unique_index(:balances, [:address_hash, :block_number]))
create(unique_index(:address_coin_balances, [:address_hash, :block_number]))
create(
unique_index(
:balances,
:address_coin_balances,
[:address_hash, :block_number],
name: :unfetched_balances,
where: "value_fetched_at IS NULL"

@ -1,18 +1,18 @@
defmodule Explorer.Chain.BalanceTest do
defmodule Explorer.Chain.Address.CoinBalanceTest do
use Explorer.DataCase
alias Ecto.Changeset
alias Explorer.Chain.Balance
alias Explorer.Chain.Address.CoinBalance
describe "changeset/2" do
test "is valid with address_hash, block_number, and value" do
params = params_for(:fetched_balance)
assert %Changeset{valid?: true} = Balance.changeset(%Balance{}, params)
assert %Changeset{valid?: true} = CoinBalance.changeset(%CoinBalance{}, params)
end
test "address_hash and block_number is required" do
assert %Changeset{valid?: false, errors: errors} = Balance.changeset(%Balance{}, %{})
assert %Changeset{valid?: false, errors: errors} = CoinBalance.changeset(%CoinBalance{}, %{})
assert is_list(errors)
assert length(errors) == 2

@ -271,20 +271,20 @@ defmodule Explorer.ChainTest do
describe "balance/2" do
test "with Address.t with :wei" do
assert Chain.balance(%Address{fetched_balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
assert Chain.balance(%Address{fetched_balance: nil}, :wei) == nil
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1)
assert Chain.balance(%Address{fetched_coin_balance: nil}, :wei) == nil
end
test "with Address.t with :gwei" do
assert Chain.balance(%Address{fetched_balance: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.balance(%Address{fetched_balance: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
assert Chain.balance(%Address{fetched_balance: nil}, :gwei) == nil
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9")
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1)
assert Chain.balance(%Address{fetched_coin_balance: nil}, :gwei) == nil
end
test "with Address.t with :ether" do
assert Chain.balance(%Address{fetched_balance: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.balance(%Address{fetched_balance: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
assert Chain.balance(%Address{fetched_balance: nil}, :ether) == nil
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18")
assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1)
assert Chain.balance(%Address{fetched_coin_balance: nil}, :ether) == nil
end
end
@ -326,6 +326,31 @@ defmodule Explorer.ChainTest do
|> Enum.map(& &1.hash)
|> Enum.reverse()
end
test "returns transactions with token_transfers preloaded" do
address = insert(:address)
block = insert(:block)
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
transaction =
:transaction
|> insert()
|> with_block(block)
insert_list(
2,
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block))
assert fetched_transaction.hash == transaction.hash
assert length(fetched_transaction.token_transfers) == 2
end
end
describe "block_to_transaction_count/1" do
@ -528,6 +553,41 @@ defmodule Explorer.ChainTest do
|> Chain.hashes_to_transactions(necessity_by_association: %{block: :optional})
|> Enum.all?(&(&1.hash in [hash_without_index1, hash_without_index2]))
end
test "returns transactions with token_transfers preloaded" do
address = insert(:address)
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
[transaction1, transaction2] =
2
|> insert_list(:transaction)
|> with_block()
%TokenTransfer{id: id1} =
insert(
:token_transfer,
to_address: address,
transaction: transaction1,
token_contract_address: token_contract_address,
token: token
)
%TokenTransfer{id: id2} =
insert(
:token_transfer,
to_address: address,
transaction: transaction2,
token_contract_address: token_contract_address,
token: token
)
fetched_transactions = Explorer.Chain.hashes_to_transactions([transaction1.hash, transaction2.hash])
assert Enum.all?(fetched_transactions, fn transaction ->
hd(transaction.token_transfers).id in [id1, id2]
end)
end
end
describe "list_blocks/2" do
@ -1106,6 +1166,45 @@ defmodule Explorer.ChainTest do
insert(:transaction)
assert [] == Explorer.Chain.recent_collated_transactions()
end
test "returns a list of recent collated transactions" do
newest_first_transactions =
50
|> insert_list(:transaction)
|> with_block()
|> Enum.reverse()
oldest_seen = Enum.at(newest_first_transactions, 9)
paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}}
recent_collated_transactions = Explorer.Chain.recent_collated_transactions(paging_options: paging_options)
assert length(recent_collated_transactions) == 10
assert hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash
end
test "returns transactions with token_transfers preloaded" do
address = insert(:address)
token_contract_address = insert(:contract_address)
token = insert(:token, contract_address: token_contract_address)
transaction =
:transaction
|> insert()
|> with_block()
insert_list(
2,
:token_transfer,
to_address: address,
transaction: transaction,
token_contract_address: token_contract_address,
token: token
)
fetched_transaction = List.first(Explorer.Chain.recent_collated_transactions())
assert fetched_transaction.hash == transaction.hash
assert length(fetched_transaction.token_transfers) == 2
end
end
describe "smart_contract_bytecode/1" do
@ -1217,7 +1316,7 @@ defmodule Explorer.ChainTest do
end
describe "stream_unfetched_balances/2" do
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do
%Address{hash: miner_hash} = miner = insert(:address)
%Block{number: block_number} = insert(:block, miner: miner)
@ -1231,7 +1330,7 @@ defmodule Explorer.ChainTest do
assert {:ok, []} = Chain.stream_unfetched_balances([], &[&1 | &2])
end
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Transaction.t/0` `from_address_hash`" do
%Address{hash: from_address_hash} = from_address = insert(:address)
%Block{number: block_number} = block = insert(:block)
@ -1261,7 +1360,7 @@ defmodule Explorer.ChainTest do
refute %{address_hash: from_address_hash, block_number: block_number} in balance_fields_list
end
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Transaction.t/0` `to_address_hash`" do
%Address{hash: to_address_hash} = to_address = insert(:address)
%Block{number: block_number} = block = insert(:block)
@ -1291,7 +1390,7 @@ defmodule Explorer.ChainTest do
refute %{address_hash: to_address_hash, block_number: block_number} in balance_fields_list
end
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.Log.t/0` `address_hash`" do
address = insert(:address)
block = insert(:block)
@ -1330,7 +1429,7 @@ defmodule Explorer.ChainTest do
} in balance_fields_list
end
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.InternalTransaction.t/0` `created_contract_address_hash`" do
created_contract_address = insert(:address)
block = insert(:block)
@ -1374,7 +1473,7 @@ defmodule Explorer.ChainTest do
} in balance_fields_list
end
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.InternalTransaction.t/0` `from_address_hash`" do
from_address = insert(:address)
block = insert(:block)
@ -1412,7 +1511,7 @@ defmodule Explorer.ChainTest do
refute %{address_hash: from_address.hash, block_number: block.number} in balance_fields_list
end
test "with `t:Explorer.Chain.Balance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <>
"does not return `t:Explorer.Chain.InternalTransaction.t/0` `to_address_hash`" do
to_address = insert(:address)
block = insert(:block)

@ -1025,4 +1025,49 @@ defmodule Explorer.EtherscanTest do
assert Etherscan.list_blocks(address.hash, options3) == []
end
end
describe "get_token_balance/2" do
test "with a single matching token_balance record" do
token_balance =
%{token_contract_address_hash: contract_address_hash, address_hash: address_hash} = insert(:token_balance)
found_token_balance = Etherscan.get_token_balance(contract_address_hash, address_hash)
assert found_token_balance.id == token_balance.id
end
test "returns token balance in latest block" do
token = insert(:token)
contract_address_hash = token.contract_address_hash
address = insert(:address)
token_details1 = %{
token_contract_address_hash: contract_address_hash,
address: address,
block_number: 5
}
token_details2 = %{
token_contract_address_hash: contract_address_hash,
address: address,
block_number: 15
}
token_details3 = %{
token_contract_address_hash: contract_address_hash,
address: address,
block_number: 10
}
_token_balance1 = insert(:token_balance, token_details1)
token_balance2 = insert(:token_balance, token_details2)
_token_balance3 = insert(:token_balance, token_details3)
found_token_balance = Etherscan.get_token_balance(contract_address_hash, address.hash)
assert found_token_balance.id == token_balance2.id
end
end
end

@ -378,7 +378,7 @@ defmodule Explorer.Chain.ImportTest do
assert changeset_errors(changeset)[:call_type] == ["can't be blank"]
end
test "publishes addresses with updated fetched_balance data to subscribers on insert" do
test "publishes addresses with updated fetched_coin_balance data to subscribers on insert" do
Chain.subscribe_to_events(:addresses)
Import.all(@import_data)
assert_received {:chain_event, :addresses, [%Address{}, %Address{}, %Address{}]}

@ -62,23 +62,6 @@ defmodule Explorer.Token.BalanceReaderTest do
)
end
defp get_balance_from_blockchain_with_balance_zero() do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: _, method: _, params: [%{data: _, to: _}]}], _options ->
{:ok,
[
%{
id: "balanceOf",
jsonrpc: "2.0",
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
]}
end
)
end
defp get_balance_from_blockchain_with_error() do
expect(
EthereumJSONRPC.Mox,

@ -11,7 +11,7 @@ defmodule Explorer.Factory do
alias Explorer.Chain.{
Address,
Address.TokenBalance,
Balance,
Address.CoinBalance,
Block,
Data,
Hash,
@ -42,15 +42,17 @@ defmodule Explorer.Factory do
end
def unfetched_balance_factory do
%Balance{
%CoinBalance{
address_hash: address_hash(),
block_number: block_number()
}
end
def update_balance_value(%Balance{address_hash: address_hash, block_number: block_number}, value) do
def update_balance_value(%CoinBalance{address_hash: address_hash, block_number: block_number}, value) do
Repo.update_all(
from(balance in Balance, where: balance.address_hash == ^address_hash and balance.block_number == ^block_number),
from(balance in CoinBalance,
where: balance.address_hash == ^address_hash and balance.block_number == ^block_number
),
set: [value: value, value_fetched_at: DateTime.utc_now()]
)
end

@ -1,6 +1,6 @@
defmodule Indexer.Balances do
defmodule Indexer.Address.CoinBalances do
@moduledoc """
Extracts `Explorer.Chain.Balance` params from other schema's params.
Extracts `Explorer.Chain.Address.CoinBalance` params from other schema's params.
"""
def params_set(%{} = import_options) do

@ -22,7 +22,7 @@ defmodule Indexer.AddressExtraction do
%{
blocks: [
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :miner_hash, to: :hash}
],
# ...
@ -40,7 +40,7 @@ defmodule Indexer.AddressExtraction do
internal_transactions: [
...,
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :created_contract_address_hash, to: :hash},
%{from: :created_contract_code, to: :contract_code}
]
@ -52,58 +52,58 @@ defmodule Indexer.AddressExtraction do
balances: [
[
%{from: :address_hash, to: :hash},
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :value, to: :fetched_balance}
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :value, to: :fetched_coin_balance}
]
],
blocks: [
[
%{from: :number, to: :fetched_balance_block_number},
%{from: :number, to: :fetched_coin_balance_block_number},
%{from: :miner_hash, to: :hash}
]
],
internal_transactions: [
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :from_address_hash, to: :hash}
],
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :to_address_hash, to: :hash}
],
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :created_contract_address_hash, to: :hash},
%{from: :created_contract_code, to: :contract_code}
]
],
transactions: [
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :from_address_hash, to: :hash}
],
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :to_address_hash, to: :hash}
]
],
logs: [
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :address_hash, to: :hash}
]
],
token_transfers: [
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :from_address_hash, to: :hash}
],
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :to_address_hash, to: :hash}
],
[
%{from: :block_number, to: :fetched_balance_block_number},
%{from: :block_number, to: :fetched_coin_balance_block_number},
%{from: :token_contract_address_hash, to: :hash}
]
]
@ -114,8 +114,8 @@ defmodule Indexer.AddressExtraction do
"""
@type params :: %{
required(:hash) => String.t(),
required(:fetched_balance_block_number) => non_neg_integer(),
optional(:fetched_balance) => non_neg_integer(),
required(:fetched_coin_balance_block_number) => non_neg_integer(),
optional(:fetched_coin_balance) => non_neg_integer(),
optional(:contract_code) => String.t()
}
@ -138,7 +138,7 @@ defmodule Indexer.AddressExtraction do
...> )
[
%{
fetched_balance_block_number: 34,
fetched_coin_balance_block_number: 34,
hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
}
]
@ -167,16 +167,16 @@ defmodule Indexer.AddressExtraction do
...> )
[
%{
fetched_balance_block_number: 1,
fetched_coin_balance_block_number: 1,
hash: "0x0000000000000000000000000000000000000001"
},
%{
fetched_balance_block_number: 2,
fetched_coin_balance_block_number: 2,
hash: "0x0000000000000000000000000000000000000002"
},
%{
contract_code: "0x",
fetched_balance_block_number: 3,
fetched_coin_balance_block_number: 3,
hash: "0x0000000000000000000000000000000000000003"
}
]
@ -200,15 +200,15 @@ defmodule Indexer.AddressExtraction do
...> )
[
%{
fetched_balance_block_number: 1,
fetched_coin_balance_block_number: 1,
hash: "0x0000000000000000000000000000000000000001"
},
%{
fetched_balance_block_number: 1,
fetched_coin_balance_block_number: 1,
hash: "0x0000000000000000000000000000000000000002"
},
%{
fetched_balance_block_number: 2,
fetched_coin_balance_block_number: 2,
hash: "0x0000000000000000000000000000000000000003"
}
]
@ -227,7 +227,7 @@ defmodule Indexer.AddressExtraction do
...> )
[
%{
fetched_balance_block_number: 1,
fetched_coin_balance_block_number: 1,
hash: "0x0000000000000000000000000000000000000001"
}
]
@ -276,7 +276,7 @@ defmodule Indexer.AddressExtraction do
...> )
[
%{
fetched_balance_block_number: 7,
fetched_coin_balance_block_number: 7,
hash: "0x0000000000000000000000000000000000000001"
}
]
@ -308,12 +308,12 @@ defmodule Indexer.AddressExtraction do
[
%{
contract_code: "0x",
fetched_balance_block_number: 3,
fetched_coin_balance_block_number: 3,
hash: "0x0000000000000000000000000000000000000001"
}
]
All data must have some way of extracting the `fetched_balance_block_number` or an `ArgumentError` will be raised when
All data must have some way of extracting the `fetched_coin_balance_block_number` or an `ArgumentError` will be raised when
none of the supported extract formats matches the params.
A contract's code is immutable: the same address cannot be bound to different code. As such, different code will
@ -401,7 +401,7 @@ defmodule Indexer.AddressExtraction do
defp extract_field(%{from: from_attribute, to: to_attribute}, item, %__MODULE__{pending: pending}) do
case Map.fetch(item, from_attribute) do
{:ok, value} when not is_nil(value) or (to_attribute == :fetched_balance_block_number and pending) ->
{:ok, value} when not is_nil(value) or (to_attribute == :fetched_coin_balance_block_number and pending) ->
{:ok, %{to_attribute => value}}
_ ->
@ -409,15 +409,18 @@ defmodule Indexer.AddressExtraction do
end
end
# Ensure that when `:addresses` or `:balances` are present, their :fetched_balance will win
# Ensure that when `:addresses` or `:balances` are present, their :fetched_coin_balance will win
defp merge_addresses(%{hash: hash} = first, %{hash: hash} = second) do
case {first[:fetched_balance], second[:fetched_balance]} do
case {first[:fetched_coin_balance], second[:fetched_coin_balance]} do
{nil, nil} ->
first
|> Map.merge(second)
|> Map.put(
:fetched_balance_block_number,
max_nil_last(Map.get(first, :fetched_balance_block_number), Map.get(second, :fetched_balance_block_number))
:fetched_coin_balance_block_number,
max_nil_last(
Map.get(first, :fetched_coin_balance_block_number),
Map.get(second, :fetched_coin_balance_block_number)
)
)
{nil, _} ->
@ -430,8 +433,8 @@ defmodule Indexer.AddressExtraction do
{_, _} ->
if greater_than_nil_last(
Map.get(first, :fetched_balance_block_number),
Map.get(second, :fetched_balance_block_number)
Map.get(first, :fetched_coin_balance_block_number),
Map.get(second, :fetched_coin_balance_block_number)
) do
# merge in `first` so its block number wins
Map.merge(second, first)

@ -6,7 +6,7 @@ defmodule Indexer.Application do
use Application
alias Indexer.{
BalanceFetcher,
CoinBalanceFetcher,
BlockFetcher,
InternalTransactionFetcher,
PendingTransactionFetcher,
@ -29,7 +29,7 @@ defmodule Indexer.Application do
children = [
{Task.Supervisor, name: Indexer.TaskSupervisor},
{BalanceFetcher, name: BalanceFetcher, json_rpc_named_arguments: json_rpc_named_arguments},
{CoinBalanceFetcher, name: CoinBalanceFetcher, json_rpc_named_arguments: json_rpc_named_arguments},
{PendingTransactionFetcher, name: PendingTransactionFetcher, json_rpc_named_arguments: json_rpc_named_arguments},
{InternalTransactionFetcher,
name: InternalTransactionFetcher, json_rpc_named_arguments: json_rpc_named_arguments},

@ -6,8 +6,8 @@ defmodule Indexer.BlockFetcher do
require Logger
alias Explorer.Chain.{Block, Import}
alias Indexer.{AddressExtraction, Balances, TokenTransfers}
alias Indexer.Address.TokenBalances
alias Indexer.{AddressExtraction, TokenTransfers}
alias Indexer.Address.{CoinBalances, TokenBalances}
alias Indexer.BlockFetcher.Receipts
@type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()}
@ -103,8 +103,8 @@ defmodule Indexer.BlockFetcher do
token_transfers: token_transfers,
transactions: transactions_with_receipts
}),
balances_params_set =
Balances.params_set(%{
coin_balances_params_set =
CoinBalances.params_set(%{
blocks_params: blocks,
logs_params: logs,
transactions_params: transactions_with_receipts
@ -116,7 +116,7 @@ defmodule Indexer.BlockFetcher do
%{
range: range,
addresses: %{params: addresses},
balances: %{params: balances_params_set},
balances: %{params: coin_balances_params_set},
token_balances: %{params: token_balances},
blocks: %{params: blocks},
logs: %{params: logs},
@ -157,7 +157,7 @@ defmodule Indexer.BlockFetcher do
callback_module.import(state, options_with_broadcast)
end
# `fetched_balance_block_number` is needed for the `BalanceFetcher`, but should not be used for `import` because the
# `fetched_balance_block_number` is needed for the `CoinBalanceFetcher`, but should not be used for `import` because the
# balance is not known yet.
defp pop_address_hash_to_fetched_balance_block_number(options) do
{address_hash_fetched_balance_block_number_pairs, import_options} =
@ -177,10 +177,10 @@ defmodule Indexer.BlockFetcher do
defp pop_hash_fetched_balance_block_number(
%{
fetched_balance_block_number: fetched_balance_block_number,
fetched_coin_balance_block_number: fetched_coin_balance_block_number,
hash: hash
} = address_params
) do
{{hash, fetched_balance_block_number}, Map.delete(address_params, :fetched_balance_block_number)}
{{hash, fetched_coin_balance_block_number}, Map.delete(address_params, :fetched_coin_balance_block_number)}
end
end

@ -10,7 +10,7 @@ defmodule Indexer.BlockFetcher.Catchup do
alias Explorer.Chain
alias Indexer.{
BalanceFetcher,
CoinBalanceFetcher,
BlockFetcher,
InternalTransactionFetcher,
Sequence,
@ -130,7 +130,7 @@ defmodule Indexer.BlockFetcher.Catchup do
block_number = Map.fetch!(address_hash_to_block_number, to_string(address_hash))
%{address_hash: address_hash, block_number: block_number}
end)
|> BalanceFetcher.async_fetch_balances()
|> CoinBalanceFetcher.async_fetch_balances()
transaction_hashes
|> Enum.map(fn transaction_hash ->

@ -229,7 +229,7 @@ defmodule Indexer.BlockFetcher.Realtime do
Enum.into(addresses_params, MapSet.new(), fn %{hash: address_hash} = address_params when is_binary(address_hash) ->
block_number =
case address_params do
%{fetched_balance_block_number: block_number} when is_integer(block_number) ->
%{fetched_coin_balance_block_number: block_number} when is_integer(block_number) ->
block_number
_ ->

@ -1,7 +1,7 @@
defmodule Indexer.BalanceFetcher do
defmodule Indexer.CoinBalanceFetcher do
@moduledoc """
Fetches `t:Explorer.Chain.Balance.t/0` and updates `t:Explorer.Chain.Address.t/0` `fetched_balance` and
`fetched_balance_block_number` to value at max `t:Explorer.Chain.Balance.t/0` `block_number` for the given `t:Explorer.Chain.Address.t/` `hash`.
Fetches `t:Explorer.Chain.Address.CoinBalance.t/0` and updates `t:Explorer.Chain.Address.t/0` `fetched_coin_balance` and
`fetched_coin_balance_block_number` to value at max `t:Explorer.Chain.Address.CoinBalance.t/0` `block_number` for the given `t:Explorer.Chain.Address.t/` `hash`.
"""
require Logger
@ -107,7 +107,7 @@ defmodule Indexer.BalanceFetcher do
|> Map.values()
|> Stream.map(&Enum.max_by(&1, fn %{block_number: block_number} -> block_number end))
|> Enum.map(fn %{address_hash: addresss_hash, block_number: block_number, value: value} ->
%{hash: addresss_hash, fetched_balance_block_number: block_number, fetched_balance: value}
%{hash: addresss_hash, fetched_coin_balance_block_number: block_number, fetched_coin_balance: value}
end)
end
end

@ -8,7 +8,7 @@ defmodule Indexer.InternalTransactionFetcher do
require Logger
alias Explorer.Chain
alias Indexer.{BalanceFetcher, AddressExtraction, BufferedTask}
alias Indexer.{CoinBalanceFetcher, AddressExtraction, BufferedTask}
alias Explorer.Chain.{Block, Hash}
@behaviour BufferedTask
@ -95,7 +95,7 @@ defmodule Indexer.InternalTransactionFetcher do
addresses_params = AddressExtraction.extract_addresses(%{internal_transactions: internal_transactions_params})
address_hash_to_block_number =
Enum.into(addresses_params, %{}, fn %{fetched_balance_block_number: block_number, hash: hash} ->
Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} ->
{hash, block_number}
end)
@ -109,7 +109,7 @@ defmodule Indexer.InternalTransactionFetcher do
block_number = Map.fetch!(address_hash_to_block_number, to_string(address_hash))
%{address_hash: address_hash, block_number: block_number}
end)
|> BalanceFetcher.async_fetch_balances()
|> CoinBalanceFetcher.async_fetch_balances()
else
{:error, step, reason, _changes_so_far} ->
Logger.debug(fn ->

@ -1,8 +1,8 @@
defmodule Indexer.BalancesTest do
defmodule Indexer.Address.CoinBalancesTest do
use ExUnit.Case, async: true
alias Explorer.Factory
alias Indexer.Balances
alias Indexer.Address.CoinBalances
describe "params_set/1" do
test "with block extracts miner_hash" do
@ -11,7 +11,7 @@ defmodule Indexer.BalancesTest do
|> to_string()
block_number = 1
params_set = Balances.params_set(%{blocks_params: [%{miner_hash: miner_hash, number: block_number}]})
params_set = CoinBalances.params_set(%{blocks_params: [%{miner_hash: miner_hash, number: block_number}]})
assert MapSet.size(params_set) == 1
assert %{address_hash: miner_hash, block_number: block_number}
@ -24,7 +24,7 @@ defmodule Indexer.BalancesTest do
|> Map.update!(:type, &to_string/1)
|> Map.put(:block_number, 1)
params_set = Balances.params_set(%{internal_transactions_params: [internal_transaction_params]})
params_set = CoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]})
assert MapSet.size(params_set) == 0
end
@ -37,7 +37,7 @@ defmodule Indexer.BalancesTest do
|> Map.put(:block_number, 1)
|> Map.put(:error, "illegal operation")
params_set = Balances.params_set(%{internal_transactions_params: [internal_transaction_params]})
params_set = CoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]})
assert MapSet.size(params_set) == 0
end
@ -56,7 +56,7 @@ defmodule Indexer.BalancesTest do
|> Map.put(:block_number, block_number)
|> Map.put(:created_contract_address_hash, created_contract_address_hash)
params_set = Balances.params_set(%{internal_transactions_params: [internal_transaction_params]})
params_set = CoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]})
assert MapSet.size(params_set) == 1
assert %{address_hash: created_contract_address_hash, block_number: block_number}
@ -81,7 +81,7 @@ defmodule Indexer.BalancesTest do
|> Map.put(:from_address_hash, from_address_hash)
|> Map.put(:to_address_hash, to_address_hash)
params_set = Balances.params_set(%{internal_transactions_params: [internal_transaction_params]})
params_set = CoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]})
assert MapSet.size(params_set) == 2
assert %{address_hash: from_address_hash, block_number: block_number}
@ -101,7 +101,7 @@ defmodule Indexer.BalancesTest do
|> Map.put(:block_number, block_number)
|> Map.put(:address_hash, address_hash)
params_set = Balances.params_set(%{logs_params: [log_params]})
params_set = CoinBalances.params_set(%{logs_params: [log_params]})
assert MapSet.size(params_set) == 1
assert %{address_hash: address_hash, block_number: block_number}
@ -120,7 +120,7 @@ defmodule Indexer.BalancesTest do
|> Map.put(:block_number, block_number)
|> Map.put(:from_address_hash, from_address_hash)
params_set = Balances.params_set(%{transactions_params: [transaction_params]})
params_set = CoinBalances.params_set(%{transactions_params: [transaction_params]})
assert MapSet.size(params_set) == 1
assert %{address_hash: from_address_hash, block_number: block_number}
@ -144,7 +144,7 @@ defmodule Indexer.BalancesTest do
|> Map.put(:from_address_hash, from_address_hash)
|> Map.put(:to_address_hash, to_address_hash)
params_set = Balances.params_set(%{transactions_params: [transaction_params]})
params_set = CoinBalances.params_set(%{transactions_params: [transaction_params]})
assert MapSet.size(params_set) == 2
assert %{address_hash: from_address_hash, block_number: block_number}

@ -74,7 +74,7 @@ defmodule Indexer.AddressExtractionTest do
]
}) == [
%{
fetched_balance_block_number: 2,
fetched_coin_balance_block_number: 2,
contract_code: "0x1",
hash: "0x0000000000000000000000000000000000000001"
}
@ -116,34 +116,34 @@ defmodule Indexer.AddressExtractionTest do
}
assert AddressExtraction.extract_addresses(blockchain_data) == [
%{hash: block.miner_hash, fetched_balance_block_number: block.number},
%{hash: block.miner_hash, fetched_coin_balance_block_number: block.number},
%{
hash: internal_transaction.from_address_hash,
fetched_balance_block_number: internal_transaction.block_number
fetched_coin_balance_block_number: internal_transaction.block_number
},
%{
hash: internal_transaction.to_address_hash,
fetched_balance_block_number: internal_transaction.block_number
fetched_coin_balance_block_number: internal_transaction.block_number
},
%{
hash: internal_transaction.created_contract_address_hash,
contract_code: internal_transaction.created_contract_code,
fetched_balance_block_number: internal_transaction.block_number
fetched_coin_balance_block_number: internal_transaction.block_number
},
%{hash: transaction.from_address_hash, fetched_balance_block_number: transaction.block_number},
%{hash: transaction.to_address_hash, fetched_balance_block_number: transaction.block_number},
%{hash: log.address_hash, fetched_balance_block_number: log.block_number},
%{hash: transaction.from_address_hash, fetched_coin_balance_block_number: transaction.block_number},
%{hash: transaction.to_address_hash, fetched_coin_balance_block_number: transaction.block_number},
%{hash: log.address_hash, fetched_coin_balance_block_number: log.block_number},
%{
hash: token_transfer.from_address_hash,
fetched_balance_block_number: token_transfer.block_number
fetched_coin_balance_block_number: token_transfer.block_number
},
%{
hash: token_transfer.to_address_hash,
fetched_balance_block_number: token_transfer.block_number
fetched_coin_balance_block_number: token_transfer.block_number
},
%{
hash: token_transfer.token_contract_address_hash,
fetched_balance_block_number: token_transfer.block_number
fetched_coin_balance_block_number: token_transfer.block_number
}
]
end
@ -178,7 +178,7 @@ defmodule Indexer.AddressExtractionTest do
assert AddressExtraction.extract_addresses(blockchain_data) ==
[
%{hash: hash, fetched_balance_block_number: 34, contract_code: "code"}
%{hash: hash, fetched_coin_balance_block_number: 34, contract_code: "code"}
]
end
@ -189,7 +189,7 @@ defmodule Indexer.AddressExtractionTest do
}
assert AddressExtraction.extract_addresses(blockchain_data) == [
%{fetched_balance_block_number: 34, hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"}
%{fetched_coin_balance_block_number: 34, hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"}
]
end

@ -9,7 +9,7 @@ defmodule Indexer.BlockFetcher.Catchup.SupervisorTest do
alias Explorer.Chain.Block
alias Indexer.{
AddressBalanceFetcherCase,
CoinBalanceFetcherCase,
BlockFetcher,
BoundInterval,
InternalTransactionFetcherCase,
@ -207,7 +207,7 @@ defmodule Indexer.BlockFetcher.Catchup.SupervisorTest do
assert Repo.aggregate(Block, :count, :hash) == 0
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
@ -255,7 +255,7 @@ defmodule Indexer.BlockFetcher.Catchup.SupervisorTest do
end)
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
@ -326,7 +326,7 @@ defmodule Indexer.BlockFetcher.Catchup.SupervisorTest do
end)
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)

@ -362,11 +362,11 @@ defmodule Indexer.BlockFetcher.RealtimeTest do
assert {:ok,
{%{
addresses: [
%Address{hash: first_address_hash, fetched_balance_block_number: 3_946_079},
%Address{hash: second_address_hash, fetched_balance_block_number: 3_946_079},
%Address{hash: third_address_hash, fetched_balance_block_number: 3_946_079},
%Address{hash: fourth_address_hash, fetched_balance_block_number: 3_946_080},
%Address{hash: fifth_address_hash, fetched_balance_block_number: 3_946_079}
%Address{hash: first_address_hash, fetched_coin_balance_block_number: 3_946_079},
%Address{hash: second_address_hash, fetched_coin_balance_block_number: 3_946_079},
%Address{hash: third_address_hash, fetched_coin_balance_block_number: 3_946_079},
%Address{hash: fourth_address_hash, fetched_coin_balance_block_number: 3_946_080},
%Address{hash: fifth_address_hash, fetched_coin_balance_block_number: 3_946_079}
],
balances: [
%{

@ -10,8 +10,8 @@ defmodule Indexer.BlockFetcherTest do
alias Explorer.Chain.{Address, Block, Log, Transaction, Wei}
alias Indexer.{
BalanceFetcher,
AddressBalanceFetcherCase,
CoinBalanceFetcher,
CoinBalanceFetcherCase,
BlockFetcher,
BufferedTask,
InternalTransactionFetcher,
@ -49,7 +49,7 @@ defmodule Indexer.BlockFetcherTest do
describe "import_range/2" do
setup %{json_rpc_named_arguments: json_rpc_named_arguments} do
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransactionFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
TokenBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
@ -224,15 +224,15 @@ defmodule Indexer.BlockFetcherTest do
}, :more}} = result
wait_for_tasks(InternalTransactionFetcher)
wait_for_tasks(BalanceFetcher)
wait_for_tasks(CoinBalanceFetcher)
assert Repo.aggregate(Block, :count, :hash) == 1
assert Repo.aggregate(Address, :count, :hash) == 1
address = Repo.get!(Address, address_hash)
assert address.fetched_balance == %Wei{value: Decimal.new(0)}
assert address.fetched_balance_block_number == 0
assert address.fetched_coin_balance == %Wei{value: Decimal.new(0)}
assert address.fetched_coin_balance_block_number == 0
end
)
end
@ -473,7 +473,7 @@ defmodule Indexer.BlockFetcherTest do
}} = BlockFetcher.fetch_and_import_range(block_fetcher, block_number..block_number)
wait_for_tasks(InternalTransactionFetcher)
wait_for_tasks(BalanceFetcher)
wait_for_tasks(CoinBalanceFetcher)
assert Repo.aggregate(Block, :count, :hash) == 1
assert Repo.aggregate(Address, :count, :hash) == 5
@ -482,28 +482,28 @@ defmodule Indexer.BlockFetcherTest do
first_address = Repo.get!(Address, first_address_hash)
assert first_address.fetched_balance == %Wei{value: Decimal.new(1_999_953_415_287_753_599_000)}
assert first_address.fetched_balance_block_number == block_number
assert first_address.fetched_coin_balance == %Wei{value: Decimal.new(1_999_953_415_287_753_599_000)}
assert first_address.fetched_coin_balance_block_number == block_number
second_address = Repo.get!(Address, second_address_hash)
assert second_address.fetched_balance == %Wei{value: Decimal.new(50_000_000_000_000_000)}
assert second_address.fetched_balance_block_number == block_number
assert second_address.fetched_coin_balance == %Wei{value: Decimal.new(50_000_000_000_000_000)}
assert second_address.fetched_coin_balance_block_number == block_number
third_address = Repo.get!(Address, third_address_hash)
assert third_address.fetched_balance == %Wei{value: Decimal.new(30_827_986_037_499_360_709_544)}
assert third_address.fetched_balance_block_number == block_number
assert third_address.fetched_coin_balance == %Wei{value: Decimal.new(30_827_986_037_499_360_709_544)}
assert third_address.fetched_coin_balance_block_number == block_number
fourth_address = Repo.get!(Address, fourth_address_hash)
assert fourth_address.fetched_balance == %Wei{value: Decimal.new(500_000_000_001_437_727_304)}
assert fourth_address.fetched_balance_block_number == block_number
assert fourth_address.fetched_coin_balance == %Wei{value: Decimal.new(500_000_000_001_437_727_304)}
assert fourth_address.fetched_coin_balance_block_number == block_number
fifth_address = Repo.get!(Address, fifth_address_hash)
assert fifth_address.fetched_balance == %Wei{value: Decimal.new(930_417_572_224_879_702_000)}
assert fifth_address.fetched_balance_block_number == block_number
assert fifth_address.fetched_coin_balance == %Wei{value: Decimal.new(930_417_572_224_879_702_000)}
assert fifth_address.fetched_coin_balance_block_number == block_number
EthereumJSONRPC.Parity ->
assert {:ok,
@ -560,7 +560,7 @@ defmodule Indexer.BlockFetcherTest do
}, :more}} = BlockFetcher.fetch_and_import_range(block_fetcher, block_number..block_number)
wait_for_tasks(InternalTransactionFetcher)
wait_for_tasks(BalanceFetcher)
wait_for_tasks(CoinBalanceFetcher)
assert Repo.aggregate(Block, :count, :hash) == 1
assert Repo.aggregate(Address, :count, :hash) == 2
@ -569,13 +569,13 @@ defmodule Indexer.BlockFetcherTest do
first_address = Repo.get!(Address, first_address_hash)
assert first_address.fetched_balance == %Wei{value: Decimal.new(1)}
assert first_address.fetched_balance_block_number == block_number
assert first_address.fetched_coin_balance == %Wei{value: Decimal.new(1)}
assert first_address.fetched_coin_balance_block_number == block_number
second_address = Repo.get!(Address, second_address_hash)
assert second_address.fetched_balance == %Wei{value: Decimal.new(252_460_837_000_000_000_000_000_000)}
assert second_address.fetched_balance_block_number == block_number
assert second_address.fetched_coin_balance == %Wei{value: Decimal.new(252_460_837_000_000_000_000_000_000)}
assert second_address.fetched_coin_balance_block_number == block_number
variant ->
raise ArgumentError, "Unsupport variant (#{variant})"

@ -1,5 +1,5 @@
defmodule Indexer.AddressBalanceFetcherTest do
# MUST be `async: false` so that {:shared, pid} is set for connection to allow BalanceFetcher's self-send to have
defmodule Indexer.CoinBalanceFetcherTest do
# MUST be `async: false` so that {:shared, pid} is set for connection to allow CoinBalanceFetcher's self-send to have
# connection allowed immediately.
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
@ -7,8 +7,9 @@ defmodule Indexer.AddressBalanceFetcherTest do
import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1]
import Mox
alias Explorer.Chain.{Address, Balance, Hash, Wei}
alias Indexer.{BalanceFetcher, AddressBalanceFetcherCase}
alias Explorer.Chain.{Address, Hash, Wei}
alias Explorer.Chain.Address.CoinBalance
alias Indexer.{CoinBalanceFetcher, CoinBalanceFetcherCase}
@moduletag :capture_log
@ -65,20 +66,20 @@ defmodule Indexer.AddressBalanceFetcherTest do
block = insert(:block, miner: miner, number: block_number)
insert(:unfetched_balance, address_hash: miner.hash, block_number: block_number)
assert miner.fetched_balance == nil
assert miner.fetched_balance_block_number == nil
assert miner.fetched_coin_balance == nil
assert miner.fetched_coin_balance_block_number == nil
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
fetched_address =
wait(fn ->
Repo.one!(
from(address in Address, where: address.hash == ^miner_hash and not is_nil(address.fetched_balance))
from(address in Address, where: address.hash == ^miner_hash and not is_nil(address.fetched_coin_balance))
)
end)
assert fetched_address.fetched_balance == %Wei{value: Decimal.new(fetched_balance)}
assert fetched_address.fetched_balance_block_number == block.number
assert fetched_address.fetched_coin_balance == %Wei{value: Decimal.new(fetched_balance)}
assert fetched_address.fetched_coin_balance_block_number == block.number
end
test "fetches unfetched addresses when less than max batch size", %{
@ -121,17 +122,17 @@ defmodule Indexer.AddressBalanceFetcherTest do
block = insert(:block, miner: miner, number: block_number)
insert(:unfetched_balance, address_hash: miner.hash, block_number: block_number)
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments, max_batch_size: 2)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments, max_batch_size: 2)
fetched_address =
wait(fn ->
Repo.one!(
from(address in Address, where: address.hash == ^miner_hash and not is_nil(address.fetched_balance))
from(address in Address, where: address.hash == ^miner_hash and not is_nil(address.fetched_coin_balance))
)
end)
assert fetched_address.fetched_balance == %Wei{value: Decimal.new(fetched_balance)}
assert fetched_address.fetched_balance_block_number == block.number
assert fetched_address.fetched_coin_balance == %Wei{value: Decimal.new(fetched_balance)}
assert fetched_address.fetched_coin_balance_block_number == block.number
end
end
@ -177,17 +178,17 @@ defmodule Indexer.AddressBalanceFetcherTest do
end)
end
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
assert :ok = BalanceFetcher.async_fetch_balances([%{address_hash: hash, block_number: block_number}])
assert :ok = CoinBalanceFetcher.async_fetch_balances([%{address_hash: hash, block_number: block_number}])
address =
wait(fn ->
Repo.get!(Address, hash)
end)
assert address.fetched_balance == %Wei{value: Decimal.new(fetched_balance)}
assert address.fetched_balance_block_number == block_number
assert address.fetched_coin_balance == %Wei{value: Decimal.new(fetched_balance)}
assert address.fetched_coin_balance_block_number == block_number
end
end
@ -250,19 +251,21 @@ defmodule Indexer.AddressBalanceFetcherTest do
params_list = Enum.map(block_quantities, &%{block_quantity: &1, hash_data: hash_data})
case BalanceFetcher.run(params_list, 0, json_rpc_named_arguments) do
case CoinBalanceFetcher.run(params_list, 0, json_rpc_named_arguments) do
:ok ->
balances = Repo.all(from(balance in Balance, where: balance.address_hash == ^hash_data))
balances = Repo.all(from(balance in CoinBalance, where: balance.address_hash == ^hash_data))
assert Enum.count(balances) == 2
balance_by_block_number =
Enum.into(balances, %{}, fn %Balance{block_number: block_number} = balance -> {block_number, balance} end)
Enum.into(balances, %{}, fn %CoinBalance{block_number: block_number} = balance ->
{block_number, balance}
end)
Enum.each(expected_balance_by_block_number, fn {block_number, expected_balance} ->
expected_value = %Explorer.Chain.Wei{value: Decimal.new(expected_balance)}
assert %Balance{value: ^expected_value} = balance_by_block_number[block_number]
assert %CoinBalance{value: ^expected_value} = balance_by_block_number[block_number]
end)
fetched_address = Repo.one!(from(address in Address, where: address.hash == ^hash_data))
@ -272,8 +275,8 @@ defmodule Indexer.AddressBalanceFetcherTest do
expected_fetched_balance = %Explorer.Chain.Wei{value: Decimal.new(expected_fetched_balance_value)}
assert fetched_address.fetched_balance == expected_fetched_balance
assert fetched_address.fetched_balance_block_number == expected_fetched_balance_block_number
assert fetched_address.fetched_coin_balance == expected_fetched_balance
assert fetched_address.fetched_coin_balance_block_number == expected_fetched_balance_block_number
other ->
# not all nodes behind the `https://mainnet.infura.io` pool are fully-synced. Node that aren't fully-synced
@ -292,7 +295,7 @@ defmodule Indexer.AddressBalanceFetcherTest do
end)
end
assert BalanceFetcher.run(
assert CoinBalanceFetcher.run(
[%{block_quantity: "0x1", hash_data: hash_data}, %{block_quantity: "0x1", hash_data: hash_data}],
0,
json_rpc_named_arguments

@ -5,7 +5,7 @@ defmodule Indexer.InternalTransactionFetcherTest do
import ExUnit.CaptureLog
import Mox
alias Indexer.{AddressBalanceFetcherCase, InternalTransactionFetcher, PendingTransactionFetcher}
alias Indexer.{CoinBalanceFetcherCase, InternalTransactionFetcher, PendingTransactionFetcher}
# MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow`
# it to use expectations and stubs from test's pid.
@ -62,7 +62,7 @@ defmodule Indexer.InternalTransactionFetcherTest do
end
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
start_supervised!({PendingTransactionFetcher, json_rpc_named_arguments: json_rpc_named_arguments})
wait_for_results(fn ->
@ -129,7 +129,7 @@ defmodule Indexer.InternalTransactionFetcherTest do
end
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa")
@ -161,7 +161,7 @@ defmodule Indexer.InternalTransactionFetcherTest do
end
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
AddressBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
CoinBalanceFetcherCase.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
# not a real transaction hash, so that it fails
insert(:transaction, hash: "0x0000000000000000000000000000000000000000000000000000000000000001")

@ -1,5 +1,5 @@
defmodule Indexer.AddressBalanceFetcherCase do
alias Indexer.BalanceFetcher
defmodule Indexer.CoinBalanceFetcherCase do
alias Indexer.CoinBalanceFetcher
def start_supervised!(options \\ []) when is_list(options) do
options
@ -8,9 +8,9 @@ defmodule Indexer.AddressBalanceFetcherCase do
init_chunk_size: 1,
max_batch_size: 1,
max_concurrency: 1,
name: BalanceFetcher
name: CoinBalanceFetcher
)
|> BalanceFetcher.child_spec()
|> CoinBalanceFetcher.child_spec()
|> ExUnit.Callbacks.start_supervised!()
end
end
Loading…
Cancel
Save