Merge remote-tracking branch 'origin' into vb-find-by-contract-name

pull/3058/head
Victor Baranov 5 years ago
commit 975ff48ca8
  1. 140
      .circleci/config.yml
  2. 5
      .dialyzer-ignore
  3. 6
      .tool-versions
  4. 118
      CHANGELOG.md
  5. 7
      apps/block_scout_web/README.md
  6. 4
      apps/block_scout_web/assets/.babelrc
  7. 35
      apps/block_scout_web/assets/__tests__/pages/chain.js
  8. 2
      apps/block_scout_web/assets/css/_typography.scss
  9. 3
      apps/block_scout_web/assets/css/app.scss
  10. 10
      apps/block_scout_web/assets/css/components/_dashboard-banner.scss
  11. 1
      apps/block_scout_web/assets/css/components/_navbar.scss
  12. 1
      apps/block_scout_web/assets/css/components/_tooltip.scss
  13. 1
      apps/block_scout_web/assets/css/non-critical.scss
  14. 4
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  15. 59
      apps/block_scout_web/assets/css/theme/_dai_variables.scss
  16. 7
      apps/block_scout_web/assets/css/theme/_ethercore_variables-non-critical.scss
  17. 84
      apps/block_scout_web/assets/css/theme/_ethercore_variables.scss
  18. 3
      apps/block_scout_web/assets/css/theme/_lukso_variables.scss
  19. 3
      apps/block_scout_web/assets/css/theme/_variables-non-critical.scss
  20. 1
      apps/block_scout_web/assets/css/theme/_variables.scss
  21. 29
      apps/block_scout_web/assets/js/app.js
  22. 18
      apps/block_scout_web/assets/js/chart-loader.js
  23. 29
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  24. 29
      apps/block_scout_web/assets/js/lib/awesomplete-util.js
  25. 2
      apps/block_scout_web/assets/js/lib/card_tabs.js
  26. 2
      apps/block_scout_web/assets/js/lib/clipboard_buttons.js
  27. 4
      apps/block_scout_web/assets/js/lib/coin_balance_history_chart.js
  28. 7
      apps/block_scout_web/assets/js/lib/currency.js
  29. 2
      apps/block_scout_web/assets/js/lib/indexing.js
  30. 61
      apps/block_scout_web/assets/js/lib/market_history_chart.js
  31. 57
      apps/block_scout_web/assets/js/lib/modals.js
  32. 18
      apps/block_scout_web/assets/js/lib/network_selector.js
  33. 4
      apps/block_scout_web/assets/js/lib/pretty_json.js
  34. 3
      apps/block_scout_web/assets/js/lib/smart_contract/index.js
  35. 1
      apps/block_scout_web/assets/js/lib/try_api.js
  36. 10
      apps/block_scout_web/assets/js/lib/try_eth_api.js
  37. 2
      apps/block_scout_web/assets/js/lib/utils.js
  38. 7
      apps/block_scout_web/assets/js/pages/address.js
  39. 9
      apps/block_scout_web/assets/js/pages/address/coin_balances.js
  40. 5
      apps/block_scout_web/assets/js/pages/address/internal_transactions.js
  41. 19
      apps/block_scout_web/assets/js/pages/address/logs.js
  42. 5
      apps/block_scout_web/assets/js/pages/address/transactions.js
  43. 1
      apps/block_scout_web/assets/js/pages/address/validations.js
  44. 1
      apps/block_scout_web/assets/js/pages/admin/tasks.js
  45. 3
      apps/block_scout_web/assets/js/pages/blocks.js
  46. 63
      apps/block_scout_web/assets/js/pages/chain.js
  47. 5
      apps/block_scout_web/assets/js/pages/pending_transactions.js
  48. 2
      apps/block_scout_web/assets/js/pages/read_token_contract.js
  49. 12
      apps/block_scout_web/assets/js/pages/token_counters.js
  50. 5
      apps/block_scout_web/assets/js/pages/transaction.js
  51. 3
      apps/block_scout_web/assets/js/pages/transactions.js
  52. 53
      apps/block_scout_web/assets/js/pages/verification_form.js
  53. 11
      apps/block_scout_web/assets/js/socket.js
  54. 19876
      apps/block_scout_web/assets/package-lock.json
  55. 67
      apps/block_scout_web/assets/package.json
  56. BIN
      apps/block_scout_web/assets/static/android-chrome-192x192.png
  57. BIN
      apps/block_scout_web/assets/static/android-chrome-512x512.png
  58. BIN
      apps/block_scout_web/assets/static/favicon-16x16.png
  59. BIN
      apps/block_scout_web/assets/static/favicon-32x32.png
  60. 2
      apps/block_scout_web/assets/static/images/dai_logo.svg
  61. 10139
      apps/block_scout_web/assets/static/images/ethercore_logo.svg
  62. 5382
      apps/block_scout_web/assets/static/images/ethercore_testnet_logo.svg
  63. BIN
      apps/block_scout_web/assets/static/images/favicon-16x16.png
  64. BIN
      apps/block_scout_web/assets/static/images/favicon-32x32.png
  65. 0
      apps/block_scout_web/assets/static/images/favicon.ico
  66. 2
      apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.svg
  67. 0
      apps/block_scout_web/assets/static/manifest.webmanifest
  68. BIN
      apps/block_scout_web/assets/static/mstile-150x150.png
  69. 79
      apps/block_scout_web/assets/webpack.config.js
  70. 6
      apps/block_scout_web/config/config.exs
  71. 7
      apps/block_scout_web/config/dev.exs
  72. 5
      apps/block_scout_web/config/dev.secret.exs.example
  73. 3
      apps/block_scout_web/config/prod.exs
  74. 3
      apps/block_scout_web/config/test.exs
  75. 1
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  76. 6
      apps/block_scout_web/lib/block_scout_web/channels/transaction_channel.ex
  77. 57
      apps/block_scout_web/lib/block_scout_web/checksum_address.ex
  78. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  79. 5
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  80. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  81. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  82. 100
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  83. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  84. 23
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/stats_controller.ex
  85. 3
      apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
  86. 3
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
  87. 3
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/instance/transfer_controller.ex
  88. 6
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  89. 3
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  90. 10
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
  91. 47
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
  92. 7
      apps/block_scout_web/lib/block_scout_web/endpoint.ex
  93. 92
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  94. 4
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  95. 18
      apps/block_scout_web/lib/block_scout_web/router.ex
  96. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex
  97. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  98. 6
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  99. 5
      apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
  100. 4
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,7 +3,7 @@ jobs:
build: build:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1-node-browsers - image: circleci/elixir:1.9.4-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -63,6 +63,10 @@ jobs:
command: npm install command: npm install
working_directory: "apps/block_scout_web/assets" working_directory: "apps/block_scout_web/assets"
- run:
command: npm rebuild node-sass
working_directory: "apps/block_scout_web/assets"
- save_cache: - save_cache:
key: v7-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }} key: v7-npm-install-{{ .Branch }}-{{ checksum "apps/block_scout_web/assets/package-lock.json" }}
paths: "apps/block_scout_web/assets/node_modules" paths: "apps/block_scout_web/assets/node_modules"
@ -129,7 +133,7 @@ jobs:
check_formatted: check_formatted:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -143,7 +147,7 @@ jobs:
credo: credo:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -177,7 +181,7 @@ jobs:
dialyzer: dialyzer:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -229,7 +233,7 @@ jobs:
eslint: eslint:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/node:12.13.0-browsers-legacy - image: circleci/node:12.16.1-browsers-legacy
working_directory: ~/app working_directory: ~/app
@ -247,7 +251,7 @@ jobs:
gettext: gettext:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -271,7 +275,7 @@ jobs:
jest: jest:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/node:12.13.0-browsers-legacy - image: circleci/node:12.16.1-browsers-legacy
working_directory: ~/app working_directory: ~/app
@ -286,7 +290,7 @@ jobs:
release: release:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: prod MIX_ENV: prod
@ -312,7 +316,7 @@ jobs:
sobelow: sobelow:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -336,7 +340,7 @@ jobs:
# test_geth_http_websocket: # test_geth_http_websocket:
# docker: # docker:
# # Ensure .tool-versions matches # # Ensure .tool-versions matches
# - image: circleci/elixir:1.9.1-node-browsers # - image: circleci/elixir:1.9.4-node-browsers
# environment: # environment:
# MIX_ENV: test # MIX_ENV: test
# # match POSTGRES_PASSWORD for postgres image below # # match POSTGRES_PASSWORD for postgres image below
@ -345,7 +349,7 @@ jobs:
# PGUSER: postgres # PGUSER: postgres
# ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.HTTPWebSocket" # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.HTTPWebSocket"
# ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Geth" # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Geth"
# - image: circleci/postgres:10.3-alpine # - image: circleci/postgres:10.10-alpine
# environment: # environment:
# # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database # # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
# POSTGRES_DB: explorer_test # POSTGRES_DB: explorer_test
@ -390,7 +394,7 @@ jobs:
# test_geth_mox: # test_geth_mox:
# docker: # docker:
# # Ensure .tool-versions matches # # Ensure .tool-versions matches
# - image: circleci/elixir:1.9.1-node-browsers # - image: circleci/elixir:1.9.4-node-browsers
# environment: # environment:
# MIX_ENV: test # MIX_ENV: test
# # match POSTGRES_PASSWORD for postgres image below # # match POSTGRES_PASSWORD for postgres image below
@ -399,7 +403,7 @@ jobs:
# PGUSER: postgres # PGUSER: postgres
# ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.Mox" # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Geth.Mox"
# ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
# - image: circleci/postgres:10.3-alpine # - image: circleci/postgres:10.10-alpine
# environment: # environment:
# # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database # # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
# POSTGRES_DB: explorer_test # POSTGRES_DB: explorer_test
@ -441,64 +445,64 @@ jobs:
# path: cover/excoveralls.html # path: cover/excoveralls.html
# - store_test_results: # - store_test_results:
# path: _build/test/junit # path: _build/test/junit
test_parity_http_websocket: # test_parity_http_websocket:
docker: # docker:
# Ensure .tool-versions matches # # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1-node-browsers # - image: circleci/elixir:1.9.4-node-browsers
environment: # environment:
MIX_ENV: test # MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # # match POSTGRES_PASSWORD for postgres image below
PGPASSWORD: postgres # PGPASSWORD: postgres
# match POSTGRES_USER for postgres image below # # match POSTGRES_USER for postgres image below
PGUSER: postgres # PGUSER: postgres
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.HTTPWebSocket" # ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.HTTPWebSocket"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Parity" # ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Parity"
- image: circleci/postgres:10.3-alpine # - image: circleci/postgres:10.10-alpine
environment: # environment:
# Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database # # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
POSTGRES_DB: explorer_test # POSTGRES_DB: explorer_test
# match PGPASSWORD for elixir image above # # match PGPASSWORD for elixir image above
POSTGRES_PASSWORD: postgres # POSTGRES_PASSWORD: postgres
# match PGUSER for elixir image above # # match PGUSER for elixir image above
POSTGRES_USER: postgres # POSTGRES_USER: postgres
working_directory: ~/app # working_directory: ~/app
steps: # steps:
- attach_workspace: # - attach_workspace:
at: . # at: .
- run: # - run:
command: ./bin/install_chrome_headless.sh # command: ./bin/install_chrome_headless.sh
no_output_timeout: 2400 # no_output_timeout: 2400
- run: mix local.hex --force # - run: mix local.hex --force
- run: mix local.rebar --force # - run: mix local.rebar --force
- run: # - run:
name: Wait for DB # name: Wait for DB
command: dockerize -wait tcp://localhost:5432 -timeout 1m # command: dockerize -wait tcp://localhost:5432 -timeout 1m
- run: # - run:
name: mix test --exclude no_parity # name: mix test --exclude no_parity
command: | # command: |
# Don't submit coverage report for forks, but let the build succeed # # Don't submit coverage report for forks, but let the build succeed
if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then # if [[ -z "$COVERALLS_REPO_TOKEN" ]]; then
mix coveralls.html --exclude no_parity --parallel --umbrella # mix coveralls.html --exclude no_parity --parallel --umbrella
else # else
mix coveralls.circle --exclude no_parity --parallel --umbrella || # mix coveralls.circle --exclude no_parity --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so signal done here and return original exit status # # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
(retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval) # (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
fi # fi
- store_artifacts: # - store_artifacts:
path: cover/excoveralls.html # path: cover/excoveralls.html
- store_test_results: # - store_test_results:
path: _build/test/junit # path: _build/test/junit
test_parity_mox: test_parity_mox:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1-node-browsers - image: circleci/elixir:1.9.4-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -507,7 +511,7 @@ jobs:
PGUSER: postgres PGUSER: postgres
ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox" ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Parity.Mox"
ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox"
- image: circleci/postgres:10.3-alpine - image: circleci/postgres:10.10-alpine
environment: environment:
# Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database
POSTGRES_DB: explorer_test POSTGRES_DB: explorer_test
@ -552,7 +556,7 @@ jobs:
coveralls_merge: coveralls_merge:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.9.1 - image: circleci/elixir:1.9.4
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -571,7 +575,7 @@ workflows:
# This unfortunately will only fire if all the tests pass because of how `requires` works # This unfortunately will only fire if all the tests pass because of how `requires` works
- coveralls_merge: - coveralls_merge:
requires: requires:
- test_parity_http_websocket # - test_parity_http_websocket
- test_parity_mox - test_parity_mox
# - test_geth_http_websocket # - test_geth_http_websocket
# - test_geth_mox # - test_geth_mox
@ -591,7 +595,7 @@ workflows:
- eslint - eslint
- jest - jest
- sobelow - sobelow
- test_parity_http_websocket # - test_parity_http_websocket
- test_parity_mox - test_parity_mox
# - test_geth_http_websocket # - test_geth_http_websocket
# - test_geth_mox # - test_geth_mox
@ -613,9 +617,9 @@ workflows:
- sobelow: - sobelow:
requires: requires:
- build - build
- test_parity_http_websocket: # - test_parity_http_websocket:
requires: # requires:
- build # - build
- test_parity_mox: - test_parity_mox:
requires: requires:
- build - build

@ -2,15 +2,14 @@
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0 :0: Unknown type 'Elixir.Map':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
lib/explorer/repo/prometheus_logger.ex:8 lib/explorer/repo/prometheus_logger.ex:8
lib/block_scout_web/views/layout_view.ex:175 lib/block_scout_web/views/layout_view.ex:175
lib/explorer/smart_contract/publisher_worker.ex:6 lib/explorer/smart_contract/publisher_worker.ex:6
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer()
apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:175: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() lib/block_scout_web/views/layout_view.ex:172: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true' apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true'
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true' apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
lib/block_scout_web/router.ex:1 lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:324 lib/phoenix/router.ex:324
lib/block_scout_web/views/layout_view.ex:146 lib/block_scout_web/views/layout_view.ex:143

@ -1,3 +1,3 @@
elixir 1.9.1-otp-22 elixir 1.9.4
erlang 22.0 erlang 22.2
nodejs 10.11.0 nodejs 12.14.1

@ -1,16 +1,131 @@
## Current ## Current
### Features ### Features
### Fixes
- [#3053](https://github.com/poanetwork/blockscout/pull/3053) - Fix ABI decoding in contracts methods, logs (migrate to ex_abi 0.3.0)
- [#3044](https://github.com/poanetwork/blockscout/pull/3044) - Prevent division by zero on /accounts page
- [#3043](https://github.com/poanetwork/blockscout/pull/3043) - Extract host name for split couple of indexer and web app
- [#3042](https://github.com/poanetwork/blockscout/pull/3042) - Speedup pending txs list query
- [#2944](https://github.com/poanetwork/blockscout/pull/2944), [#3046](https://github.com/poanetwork/blockscout/pull/3046) - Split js logic into multiple files
### Chore
## 3.1.0-beta
### Features
- [#3013](https://github.com/poanetwork/blockscout/pull/3013), [#3026](https://github.com/poanetwork/blockscout/pull/3026), [#3031](https://github.com/poanetwork/blockscout/pull/3031) - Raw trace of transaction on-demand
- [#3000](https://github.com/poanetwork/blockscout/pull/3000) - Get rid of storing of first trace for all types of transactions for Parity variant
- [#2875](https://github.com/poanetwork/blockscout/pull/2875) - Save contract code from Parity genesis file
- [#2834](https://github.com/poanetwork/blockscout/pull/2834), [#3009](https://github.com/poanetwork/blockscout/pull/3009), [#3014](https://github.com/poanetwork/blockscout/pull/3014), [#3033](https://github.com/poanetwork/blockscout/pull/3033) - always redirect to checksummed hash
### Fixes
- [#3037](https://github.com/poanetwork/blockscout/pull/3037) - Make buttons color at verification page consistent
- [#3034](https://github.com/poanetwork/blockscout/pull/3034) - Support stateMutability=view to define reading functions in smart-contracts
- [#3029](https://github.com/poanetwork/blockscout/pull/3029) - Fix transactions and blocks appearance on the main page
- [#3028](https://github.com/poanetwork/blockscout/pull/3028) - Decrease polling period value for realtime fetcher
- [#3027](https://github.com/poanetwork/blockscout/pull/3027) - Rescue for SUPPORTED_CHAINS env var parsing
- [#3025](https://github.com/poanetwork/blockscout/pull/3025) - Fix splitting of indexer/web components setup
- [#3024](https://github.com/poanetwork/blockscout/pull/3024) - Fix pool size default value in config
- [#3021](https://github.com/poanetwork/blockscout/pull/3021), [#3022](https://github.com/poanetwork/blockscout/pull/3022) - Refine dev/test config
- [#3016](https://github.com/poanetwork/blockscout/pull/3016), [#3017](https://github.com/poanetwork/blockscout/pull/3017) - Fix token instance QR code data
- [#3012](https://github.com/poanetwork/blockscout/pull/3012) - Speedup token transfers list query
- [#3011](https://github.com/poanetwork/blockscout/pull/3011) - Revert realtime fetcher small skips feature
- [#3007](https://github.com/poanetwork/blockscout/pull/3007) - Fix copy UTF8 tx input action
- [#2996](https://github.com/poanetwork/blockscout/pull/2996) - Fix awesomplete lib loading in Firefox
- [#2993](https://github.com/poanetwork/blockscout/pull/2993) - Fix path definition for contract verification endpoint
- [#2990](https://github.com/poanetwork/blockscout/pull/2990) - Fix import of Parity spec file
- [#2989](https://github.com/poanetwork/blockscout/pull/2989) - Introduce API_PATH env var
- [#2988](https://github.com/poanetwork/blockscout/pull/2988) - Fix web manifest accessibility
- [#2967](https://github.com/poanetwork/blockscout/pull/2967) - Fix styles loading for firefox
- [#2950](https://github.com/poanetwork/blockscout/pull/2950) - Add `creationMethod` to `EthereumJSONRPC.Parity.Trace.Action.entry_to_elixir`
- [#2897](https://github.com/poanetwork/blockscout/pull/2897) - remove duplicate indexes
- [#2883](https://github.com/poanetwork/blockscout/pull/2883) - Fix long contracts names
### Chore
- [#3032](https://github.com/poanetwork/blockscout/pull/3032) - Remove indexing status alert for Ganache variant
- [#3030](https://github.com/poanetwork/blockscout/pull/3030) - Remove default websockets URL from config
- [#2995](https://github.com/poanetwork/blockscout/pull/2995) - Support API_PATH env var in Docker file
## 3.0.0-beta
### Features
- [#2835](https://github.com/poanetwork/blockscout/pull/2835), [#2871](https://github.com/poanetwork/blockscout/pull/2871), [#2872](https://github.com/poanetwork/blockscout/pull/2872), [#2886](https://github.com/poanetwork/blockscout/pull/2886), [#2925](https://github.com/poanetwork/blockscout/pull/2925), [#2936](https://github.com/poanetwork/blockscout/pull/2936), [#2949](https://github.com/poanetwork/blockscout/pull/2949), [#2940](https://github.com/poanetwork/blockscout/pull/2940), [#2958](https://github.com/poanetwork/blockscout/pull/2958) - Add "block_hash" to logs, token_transfers and internal transactions and "pending blocks operations" approach
- [#2975](https://github.com/poanetwork/blockscout/pull/2975) - Refine UX of contracts verification
- [#2926](https://github.com/poanetwork/blockscout/pull/2926) - API endpoint: sum balances except burnt address
- [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action explicitly
### Fixes
- [#2969](https://github.com/poanetwork/blockscout/pull/2969) - Fix contract constructor require msg appearance in constructor arguments encoded view
- [#2964](https://github.com/poanetwork/blockscout/pull/2964) - Fix bug in skipping of constructor arguments in contract verification
- [#2961](https://github.com/poanetwork/blockscout/pull/2961) - Add a guard that addresses is enum in `values` function in `read contract` page
- [#2960](https://github.com/poanetwork/blockscout/pull/2960) - Add BLOCKSCOUT_HOST to docker setup
- [#2956](https://github.com/poanetwork/blockscout/pull/2956) - Add support of 0.6.x version of compiler
- [#2955](https://github.com/poanetwork/blockscout/pull/2955) - Move socket path to env
- [#2938](https://github.com/poanetwork/blockscout/pull/2938) - utf8 copy tx input tooltip
- [#2934](https://github.com/poanetwork/blockscout/pull/2934) - RSK release 1.2.0 breaking changes support
- [#2933](https://github.com/poanetwork/blockscout/pull/2933) - Get rid of deadlock in the query to address_current_token_balance table
- [#2932](https://github.com/poanetwork/blockscout/pull/2932) - fix duplicate websocket connection
- [#2928](https://github.com/poanetwork/blockscout/pull/2928) - Speedup pending block ops int txs to fetch query
- [#2924](https://github.com/poanetwork/blockscout/pull/2924) - Speedup address to logs query
- [#2915](https://github.com/poanetwork/blockscout/pull/2915) - Speedup of blocks_without_reward_query
- [#2914](https://github.com/poanetwork/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query
- [#2910](https://github.com/poanetwork/blockscout/pull/2910) - Reorganize queries and indexes for internal_transactions table
- [#2908](https://github.com/poanetwork/blockscout/pull/2908) - Fix performance of address page
- [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache
- [#2902](https://github.com/poanetwork/blockscout/pull/2902) - Offset in blocks retrieval for average block time
- [#2900](https://github.com/poanetwork/blockscout/pull/2900) - check fetched instance metadata in multiple places
- [#2899](https://github.com/poanetwork/blockscout/pull/2899) - fix empty buffered task
- [#2887](https://github.com/poanetwork/blockscout/pull/2887) - increase chart loading speed
### Chore
- [#2959](https://github.com/poanetwork/blockscout/pull/2959) - Remove logs from test folder too in the cleaning script
- [#2954](https://github.com/poanetwork/blockscout/pull/2954) - Upgrade absinthe and ecto deps
- [#2947](https://github.com/poanetwork/blockscout/pull/2947) - Upgrade Circle CI postgres Docker image
- [#2946](https://github.com/poanetwork/blockscout/pull/2946) - Fix vulnerable NPM deps
- [#2942](https://github.com/poanetwork/blockscout/pull/2942) - Actualize Docker setup
- [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests
- [#2873](https://github.com/poanetwork/blockscout/pull/2873) - bump elixir to 1.9.4
## 2.1.1-beta
### Features
- [#2862](https://github.com/poanetwork/blockscout/pull/2862) - Coin total supply from DB API endpoint
- [#2857](https://github.com/poanetwork/blockscout/pull/2857) - Extend getsourcecode API view with new output fields
- [#2822](https://github.com/poanetwork/blockscout/pull/2822) - Estimated address count on the main page, if cache is empty
- [#2821](https://github.com/poanetwork/blockscout/pull/2821) - add autodetection of constructor arguments
- [#2825](https://github.com/poanetwork/blockscout/pull/2825) - separate token transfers and transactions
- [#2787](https://github.com/poanetwork/blockscout/pull/2787) - async fetching of address counters - [#2787](https://github.com/poanetwork/blockscout/pull/2787) - async fetching of address counters
- [#2791](https://github.com/poanetwork/blockscout/pull/2791) - add ipc client - [#2791](https://github.com/poanetwork/blockscout/pull/2791) - add ipc client
- [#2449](https://github.com/poanetwork/blockscout/pull/2449) - add ability to send notification events through postgres notify - [#2449](https://github.com/poanetwork/blockscout/pull/2449) - add ability to send notification events through postgres notify
### Fixes ### Fixes
- [#2864](https://github.com/poanetwork/blockscout/pull/2864) - add token instance metadata type check
- [#2855](https://github.com/poanetwork/blockscout/pull/2855) - Fix favicons load
- [#2854](https://github.com/poanetwork/blockscout/pull/2854) - Fix all npm vulnerabilities
- [#2851](https://github.com/poanetwork/blockscout/pull/2851) - Fix paths for front assets
- [#2843](https://github.com/poanetwork/blockscout/pull/2843) - fix realtime fetcher small skips feature
- [#2841](https://github.com/poanetwork/blockscout/pull/2841) - LUKSO dashboard height fix
- [#2837](https://github.com/poanetwork/blockscout/pull/2837) - fix txlist ordering issue
- [#2830](https://github.com/poanetwork/blockscout/pull/2830) - Fix wrong color of contract icon on xDai chain
- [#2829](https://github.com/poanetwork/blockscout/pull/2829) - Fix for stuck gas limit label and value
- [#2828](https://github.com/poanetwork/blockscout/pull/2828) - Fix for script that clears compilation/launching assets
- [#2800](https://github.com/poanetwork/blockscout/pull/2800) - return not found for not verified contract for token read_contract - [#2800](https://github.com/poanetwork/blockscout/pull/2800) - return not found for not verified contract for token read_contract
- [#2806](https://github.com/poanetwork/blockscout/pull/2806) - Fix blocks fetching on the main page - [#2806](https://github.com/poanetwork/blockscout/pull/2806) - Fix blocks fetching on the main page
- [#2803](https://github.com/poanetwork/blockscout/pull/2803) - Fix block validator custom tooltip - [#2803](https://github.com/poanetwork/blockscout/pull/2803) - Fix block validator custom tooltip
- [#2748](https://github.com/poanetwork/blockscout/pull/2748) - Rewrite token updater
- [#2704](https://github.com/poanetwork/blockscout/pull/2704) - refetch null values in token balances
- [#2690](https://github.com/poanetwork/blockscout/pull/2690) - do not stich json rpc config into module for net version cache
### Chore ### Chore
- [#2878](https://github.com/poanetwork/blockscout/pull/2878) - Decrease loaders showing delay on the main page
- [#2859](https://github.com/poanetwork/blockscout/pull/2859) - Add eth_blockNumber API endpoint to eth_rpc section
- [#2846](https://github.com/poanetwork/blockscout/pull/2846) - Remove networks images preload
- [#2845](https://github.com/poanetwork/blockscout/pull/2845) - Set outline none for nav dropdown item in mobile view (fix for Safari)
- [#2844](https://github.com/poanetwork/blockscout/pull/2844) - Extend external reward types up to 20
- [#2827](https://github.com/poanetwork/blockscout/pull/2827) - Node js 12.13.0 (latest LTS release) support - [#2827](https://github.com/poanetwork/blockscout/pull/2827) - Node js 12.13.0 (latest LTS release) support
- [#2818](https://github.com/poanetwork/blockscout/pull/2818) - allow hiding marketcap percentage - [#2818](https://github.com/poanetwork/blockscout/pull/2818) - allow hiding marketcap percentage
- [#2817](https://github.com/poanetwork/blockscout/pull/2817) - move docker integration documentation to blockscout docs - [#2817](https://github.com/poanetwork/blockscout/pull/2817) - move docker integration documentation to blockscout docs
@ -46,8 +161,10 @@
- [#2799](https://github.com/poanetwork/blockscout/pull/2799) - fix catchup fetcher for empty node and db - [#2799](https://github.com/poanetwork/blockscout/pull/2799) - fix catchup fetcher for empty node and db
- [#2783](https://github.com/poanetwork/blockscout/pull/2783) - Fix stuck value and ticker on the token page - [#2783](https://github.com/poanetwork/blockscout/pull/2783) - Fix stuck value and ticker on the token page
- [#2781](https://github.com/poanetwork/blockscout/pull/2781) - optimize txlist json rpc - [#2781](https://github.com/poanetwork/blockscout/pull/2781) - optimize txlist json rpc
- [#2777](https://github.com/poanetwork/blockscout/pull/2777) - Remove duplicate blocks from changes_list before import
- [#2770](https://github.com/poanetwork/blockscout/pull/2770) - do not re-fetch token instances without uris - [#2770](https://github.com/poanetwork/blockscout/pull/2770) - do not re-fetch token instances without uris
- [#2769](https://github.com/poanetwork/blockscout/pull/2769) - optimize token token transfers query - [#2769](https://github.com/poanetwork/blockscout/pull/2769) - optimize token token transfers query
- [#2768](https://github.com/poanetwork/blockscout/pull/2768) - Remove nonconsensus blocks from cache after internal transactions importing
- [#2761](https://github.com/poanetwork/blockscout/pull/2761) - add indexes for token instances fetching queries - [#2761](https://github.com/poanetwork/blockscout/pull/2761) - add indexes for token instances fetching queries
- [#2767](https://github.com/poanetwork/blockscout/pull/2767) - fix websocket subscriptions with token instances - [#2767](https://github.com/poanetwork/blockscout/pull/2767) - fix websocket subscriptions with token instances
- [#2765](https://github.com/poanetwork/blockscout/pull/2765) - fixed width issue for cards in mobile view for Transaction Details page - [#2765](https://github.com/poanetwork/blockscout/pull/2765) - fixed width issue for cards in mobile view for Transaction Details page
@ -90,6 +207,7 @@ fixed menu hovers in dark mode desktop view
- [#2724](https://github.com/poanetwork/blockscout/pull/2724) - fix ci by commenting a line in hackney library - [#2724](https://github.com/poanetwork/blockscout/pull/2724) - fix ci by commenting a line in hackney library
- [#2708](https://github.com/poanetwork/blockscout/pull/2708) - add log index to logs view - [#2708](https://github.com/poanetwork/blockscout/pull/2708) - add log index to logs view
- [#2723](https://github.com/poanetwork/blockscout/pull/2723) - get rid of ex_json_schema warnings - [#2723](https://github.com/poanetwork/blockscout/pull/2723) - get rid of ex_json_schema warnings
- [#2740](https://github.com/poanetwork/blockscout/pull/2740) - add verify contract rpc doc
## 2.0.4-beta ## 2.0.4-beta

@ -4,9 +4,9 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we
## Machine Requirements ## Machine Requirements
* Erlang/OTP 20.2+ * Erlang/OTP 21+
* Elixir 1.5+ * Elixir 1.9+
* Postgres 10.0 * Postgres 10.3
## Required Accounts ## Required Accounts
@ -21,7 +21,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we
To get BlockScout Web interface up and running locally: To get BlockScout Web interface up and running locally:
* Setup `../explorer` * Setup `../explorer`
* Set up some default configuration with: `$ cp config/dev.secret.exs.example config/dev.secret.esx`
* Install Node.js dependencies with `$ cd assets && npm install && cd ..` * Install Node.js dependencies with `$ cd assets && npm install && cd ..`
* Start Phoenix with `$ mix phx.server` (This can be run from this directory or the project root: the project root is recommended.) * Start Phoenix with `$ mix phx.server` (This can be run from this directory or the project root: the project root is recommended.)

@ -1,5 +1,3 @@
{ {
presets: [ "presets": [["@babel/preset-env", { "useBuiltIns": "usage" }]]
'env'
]
} }

@ -61,6 +61,36 @@ describe('RECEIVED_NEW_BLOCK', () => {
expect(output.averageBlockTime).toEqual('5 seconds') expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.blocks).toEqual([ expect(output.blocks).toEqual([
{ blockNumber: 2, chainBlockHtml: 'new block', averageBlockTime: '5 seconds' }, { blockNumber: 2, chainBlockHtml: 'new block', averageBlockTime: '5 seconds' },
{ blockNumber: 1, chainBlockHtml: 'test 1' },
{ blockNumber: 0, chainBlockHtml: 'test 0' }
])
})
test('receives new block if >= 4 blocks', () => {
const state = Object.assign({}, initialState, {
averageBlockTime: '6 seconds',
blocks: [
{ blockNumber: 3, chainBlockHtml: 'test 3' },
{ blockNumber: 2, chainBlockHtml: 'test 2' },
{ blockNumber: 1, chainBlockHtml: 'test 1' },
{ blockNumber: 0, chainBlockHtml: 'test 0' }
]
})
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
averageBlockTime: '5 seconds',
blockNumber: 4,
chainBlockHtml: 'new block'
}
}
const output = reducer(state, action)
expect(output.averageBlockTime).toEqual('5 seconds')
expect(output.blocks).toEqual([
{ blockNumber: 4, chainBlockHtml: 'new block', averageBlockTime: '5 seconds' },
{ blockNumber: 3, chainBlockHtml: 'test 3' },
{ blockNumber: 2, chainBlockHtml: 'test 2' },
{ blockNumber: 1, chainBlockHtml: 'test 1' } { blockNumber: 1, chainBlockHtml: 'test 1' }
]) ])
}) })
@ -318,7 +348,8 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => {
}) })
test('single transaction after large batch of transactions', () => { test('single transaction after large batch of transactions', () => {
const state = Object.assign({}, initialState, { const state = Object.assign({}, initialState, {
transactionsBatch: [1,2,3,4,5,6,7,8,9,10,11] transactionsBatch: [6,7,8,9,10,11,12,13,14,15,16],
transactions: [1,2,3,4,5]
}) })
const action = { const action = {
type: 'RECEIVED_NEW_TRANSACTION_BATCH', type: 'RECEIVED_NEW_TRANSACTION_BATCH',
@ -328,7 +359,7 @@ describe('RECEIVED_NEW_TRANSACTION_BATCH', () => {
} }
const output = reducer(state, action) const output = reducer(state, action)
expect(output.transactions).toEqual([]) expect(output.transactions).toEqual([1,2,3,4,5])
expect(output.transactionsBatch.length).toEqual(12) expect(output.transactionsBatch.length).toEqual(12)
}) })
test('large batch of transactions after large batch of transactions', () => { test('large batch of transactions after large batch of transactions', () => {

@ -3,7 +3,7 @@ $blue: #4b89fb !default;
$success: #34c0ad !default; $success: #34c0ad !default;
body { body {
font-family: $font-family-sans-serif; font-family: $font-family;
font-size: 12px; font-size: 12px;
} }

@ -16,6 +16,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
// Bootstrap Core CSS // Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions"; @import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/variables";
@import "node_modules/bootstrap/scss/mixins"; @import "node_modules/bootstrap/scss/mixins";
@import "theme/variables"; @import "theme/variables";
@ -60,7 +61,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
// Custom SCSS // Custom SCSS
@import "layout"; @import "layout";
@import "typography"; @import "typography";
@import "images-preload";
@import "code"; @import "code";
@import "helpers"; @import "helpers";
@import "elements"; @import "elements";
@ -110,7 +110,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/log-search"; @import "components/log-search";
@import "components/radio"; @import "components/radio";
@import "components/modal_variables"; @import "components/modal_variables";
@import "components/network-selector";
@import "components/new_smart_contract"; @import "components/new_smart_contract";
@import "components/radio_big"; @import "components/radio_big";
@import "components/btn_no_border"; @import "components/btn_no_border";

@ -53,14 +53,16 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
.dashboard-banner-chart { .dashboard-banner-chart {
flex-grow: 1; flex-grow: 1;
margin: 0 0 35px 0; margin: 15px 0 20px 0;
max-width: 350px; max-width: 350px;
position: relative; position: relative;
min-height: 100px;
height: calc(100% - 86px);
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
flex-grow: 0; flex-grow: 0;
margin-bottom: 20px; margin-top: 20px;
margin-top: auto; margin-bottom: auto;
max-width: 100%; max-width: 100%;
} }
@ -227,4 +229,4 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
font-size: 0.9rem; font-size: 0.9rem;
} }
} }
} }

@ -25,6 +25,7 @@ $navbar-logo-width: auto !default;
.navbar-nav { .navbar-nav {
.nav-link { .nav-link {
outline: none;
align-items: center; align-items: center;
color: $header-links-color; color: $header-links-color;
display: flex; display: flex;

@ -10,6 +10,7 @@ $tooltip-color: #fff !default;
border-radius: 5px; border-radius: 5px;
color: $tooltip-color; color: $tooltip-color;
padding: 15px; padding: 15px;
font-size: 12px;
} }
.arrow::before { .arrow::before {

@ -1,5 +1,6 @@
// Bootstrap Core CSS // Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions"; @import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/variables";
@import "node_modules/bootstrap/scss/mixins"; @import "node_modules/bootstrap/scss/mixins";
@import "theme/variables-non-critical"; @import "theme/variables-non-critical";

@ -255,11 +255,11 @@ $transition-cont: all 0.4s ease-in-out !default;
// Font, line-height, and color for body text, headings, and more. // Font, line-height, and color for body text, headings, and more.
// stylelint-disable value-keyword-case // stylelint-disable value-keyword-case
$font-family-sans-serif: Nunito, "Helvetica Neue", Arial, sans-serif, $font-family: Nunito, "Helvetica Neue", Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, $font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace !default; "Liberation Mono", "Courier New", monospace !default;
$font-family-base: $font-family-sans-serif !default; $font-family-base: $font-family !default;
// stylelint-enable value-keyword-case // stylelint-enable value-keyword-case
$font-size-base: 1rem !default; // Assumes the browser default, typically `16px` $font-size-base: 1rem !default; // Assumes the browser default, typically `16px`

@ -1,23 +1,23 @@
// general // general
$primary: #233174; $primary: #17314f;
$secondary: #15f9bb; $secondary: #15bba6;
$tertiary: #5a77ff; $tertiary: #93d7ff;
$additional-font: #fff; $additional-font: #fff;
// footer // footer
$footer-background-color:#202d6a; $footer-background-color: $primary;
$footer-title-color: #fff; $footer-title-color: #fff;
$footer-text-color: #b5c2ff; $footer-text-color: #96bde8;
$footer-item-disc-color: $secondary; $footer-item-disc-color: $secondary;
.footer-logo { filter: brightness(0) invert(1); } .footer-logo { filter: brightness(0) invert(1); }
.footer-social-icon { color: $secondary!important; }
// dashboard // dashboard
$dashboard-line-color-price: $tertiary; // price left border $dashboard-line-color-price: $tertiary; // price left border
$dashboard-line-color-market: $secondary; $dashboard-line-color-market: $secondary;
$dashboard-banner-chart-legend-label-color: $footer-text-color; $dashboard-banner-chart-legend-label-color: $footer-text-color;
$dashboard-stats-item-label-color: #b4c1ff; $dashboard-stats-item-label-color: $footer-text-color;
$dashboard-banner-chart-legend-value-color: #fff; // chart labels $dashboard-banner-chart-legend-value-color: #fff; // chart labels
$dashboard-stats-item-value-color: #fff; // stat values $dashboard-stats-item-value-color: #fff; // stat values
@ -25,11 +25,9 @@ $dashboard-stats-item-border-color: $secondary; // stat border
$dashboard-banner-gradient-start: $primary; // gradient begin $dashboard-banner-gradient-start: $primary; // gradient begin
$dashboard-banner-gradient-end: #1e2a63; $dashboard-banner-gradient-end: lighten($primary, 5); // gradient end
// gradient end
$dashboard-banner-network-plain-container-background-color: #273781 $dashboard-banner-network-plain-container-background-color: #20446e; // stats bg
; // stats bg
// navigation // navigation
@ -37,20 +35,21 @@ $dashboard-banner-network-plain-container-background-color: #273781
// buttons // buttons
$btn-line-bg: #fff; // button bg $btn-line-bg: #fff; // button bg
$btn-line-color: $primary; // button border and font color && hover bg color $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $primary; // btn copy $btn-copy-color: $secondary; // btn copy
$btn-qr-color: $primary; // btn qr-code $btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color $btn-address-card-icon-color: $secondary; // btn address color
$btn-contract-color: $secondary;
//links & tile //links & tile
$tile-body-a-color: $tertiary; $tile-body-a-color: $secondary;
$tile-type-block-color: $primary; $tile-type-block-color: $secondary;
$tile-type-progress-bar-color: $primary; $tile-type-progress-bar-color: $secondary;
a.tile-title { color: $primary !important; } a.tile-title { color: $secondary !important; }
// card // card
$card-background-1: $primary; $card-background-1: $secondary;
$card-tab-active: $primary; $card-tab-active: $secondary;
.layout-container { .layout-container {
.dashboard-banner-container { .dashboard-banner-container {
@ -68,20 +67,6 @@ $badge-neutral-background-color: rgba(#20446e, .1);
$api-text-monospace-color: #20446e; $api-text-monospace-color: #20446e;
// Dark theme // Dark theme
$dark-primary: #233174; $dark-primary: #15bba6;
$dark-secondary:#15f9bb; $dark-secondary: #93d7ff;
$dark-tertiary: #5a77ff; $dark-primary-alternate: #15bba6;
.dark-theme-applied .tile .tile-body a, .dark-theme-applied .tile span[data-address-hash] {
color: $dark-tertiary!important;
}
.dark-theme-applied .btn-line {
background-color: transparent!important;
border-color: $dark-tertiary!important;
color: $dark-tertiary!important;
}
.dark-theme-applied .btn-line:hover {
color: $additional-font!important;
}

@ -0,0 +1,7 @@
// general
$primary: #3a6ea7;
$secondary: #558ac3;
$tertiary: #3a6ea7;
$additional-font: #bdbdff;
$btn-line-color: $tertiary; // button border and font color && hover bg color

@ -0,0 +1,84 @@
// general
$primary: #3a6ea7;
$secondary: #558ac3;
$tertiary: #3a6ea7;
$additional-font: #bdbdff;
// footer
$footer-background-color: $primary;
$footer-title-color: #fff;
$footer-text-color: $additional-font;
$footer-item-disc-color: $tertiary;
$footer-social-icon-color: #3a6ea7;
// dashboard
$dashboard-line-color-price: $secondary;
$dashboard-line-color-market: $tertiary;
$dashboard-banner-gradient-start: #7ba4d1;
$dashboard-banner-gradient-end: #3a6ea7;
$dashboard-banner-chart-legend-label-color: $additional-font;
$dashboard-stats-item-label-color: $additional-font;
$dashboard-banner-chart-legend-value-color: #fff; // chart labels
$dashboard-stats-item-value-color: #fff; // stat values
$dashboard-stats-item-border-color: $secondary; // stat border
$dashboard-banner-network-plain-container-background-color: #2e5884; // stats bg
// navigation
$header-icon-border-color-hover: $tertiary; // top border on hover
$header-icon-color-hover: $tertiary; // nav icon on hover
// buttons
$btn-line-bg: #fff; // button bg
$btn-line-color: $tertiary; // button border and font color && hover bg color
$btn-copy-color: $tertiary; // btn copy
$btn-qr-color: $tertiary; // btn qr-code
$btn-address-card-icon-color: $tertiary; // btn address color
$btn-contract-color: $tertiary;
//links & tile
$tile-body-a-color: $tertiary;
$tile-type-block-color: $tertiary;
$tile-type-progress-bar-color: $tertiary;
a.tile-title { color: $tertiary !important; }
// card
$card-background-1: $tertiary;
$card-tab-active: $tertiary;
// ETC theme's idiosyncrasies
.layout-container {
.navbar {
box-shadow: 0 0 30px 0 rgba(21, 53, 80, 0.12);
}
.dropdown-item:hover,
.dropdown-item.active,
.dropdown-item:focus {
background-color: rgba($tertiary, .1) !important;
color: $tertiary !important;
}
.dashboard-banner-container {
background-image: linear-gradient(
to bottom,
$dashboard-banner-gradient-start,
$dashboard-banner-gradient-end
);
}
.footer-logo {
filter: brightness(0) invert(1);
}
}
// Badges
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary;
// Dark theme
$dark-primary: #8588ff;
$dark-secondary: #4ad7a7;
$dark-primary-alternate: #5b5ed8;

@ -56,6 +56,9 @@ $dashboard-banner-network-stats-static-image: "/images/lukso_dashboard_image.png
$dashboard-banner-network-plain-container-height: 150px; $dashboard-banner-network-plain-container-height: 150px;
.layout-container { .layout-container {
.dashboard-banner-container {
height: auto !important;
}
.dashboard-banner-container::after { .dashboard-banner-container::after {
display: none; display: none;
} }

@ -20,4 +20,5 @@
// @import "sokol_variables-non-critical"; // @import "sokol_variables-non-critical";
// @import "tobalaba_variables-non-critical"; // @import "tobalaba_variables-non-critical";
// @import "tomochain_variables-non-critical"; // @import "tomochain_variables-non-critical";
// @import "rsk_variables-non-critical"; // @import "rsk_variables-non-critical";
// @import "ethercore_variables-non-critical";

@ -21,6 +21,7 @@
// @import "tobalaba_variables"; // @import "tobalaba_variables";
// @import "tomochain_variables"; // @import "tomochain_variables";
// @import "rsk_variables"; // @import "rsk_variables";
// @import "ethercore_variables";
// responsive breakpoints // responsive breakpoints
$breakpoint-xs: 320px; $breakpoint-xs: 320px;

@ -9,7 +9,6 @@ import '../css/app.scss'
// //
// Import dependencies // Import dependencies
// //
import '@babel/polyfill'
import 'phoenix_html' import 'phoenix_html'
import 'bootstrap' import 'bootstrap'
@ -20,46 +19,18 @@ import 'bootstrap'
import './locale' import './locale'
// support of preload in Firefox
import '../node_modules/fg-loadcss/dist/cssrelpreload.min'
import './pages/address'
import './pages/address/coin_balances'
import './pages/address/transactions'
import './pages/address/logs'
import './pages/address/validations'
import './pages/address/internal_transactions'
import './pages/blocks'
import './pages/chain'
import './pages/pending_transactions'
import './pages/transaction'
import './pages/transactions'
import './pages/layout' import './pages/layout'
import './pages/verification_form'
import './pages/token_counters'
import './pages/dark-mode-switcher' import './pages/dark-mode-switcher'
import './pages/admin/tasks.js'
import './lib/clipboard_buttons' import './lib/clipboard_buttons'
import './lib/currency' import './lib/currency'
import './lib/from_now' import './lib/from_now'
import './lib/indexing' import './lib/indexing'
import './lib/loading_element' import './lib/loading_element'
import './lib/market_history_chart'
import './lib/pending_transactions_toggle' import './lib/pending_transactions_toggle'
import './lib/pretty_json' import './lib/pretty_json'
import './lib/reload_button' import './lib/reload_button'
import './lib/smart_contract/read_only_functions'
import './lib/smart_contract/wei_ether_converter'
import './lib/stop_propagation' import './lib/stop_propagation'
import './lib/token_balance_dropdown'
import './lib/token_balance_dropdown_search'
import './lib/token_transfers_toggle'
import './lib/transaction_input_dropdown'
import './lib/async_listing_load'
import './lib/tooltip' import './lib/tooltip'
import './lib/modals' import './lib/modals'
import './lib/try_api'
import './lib/try_eth_api'
import './lib/card_tabs' import './lib/card_tabs'

@ -0,0 +1,18 @@
import $ from 'jquery'
import { formatAllUsdValues, updateAllCalculatedUsdValues } from './lib/currency'
import { createMarketHistoryChart } from './lib/market_history_chart'
import { createCoinBalanceHistoryChart } from './lib/coin_balance_history_chart'
(function () {
const dashboardChartElement = $('[data-chart="marketHistoryChart"]')[0]
const coinBalanceHistoryChartElement = $('[data-chart="coinBalanceHistoryChart"]')[0]
if (dashboardChartElement) {
window.dashboardChart = createMarketHistoryChart(dashboardChartElement)
}
if (coinBalanceHistoryChartElement) {
window.coinBalanceHistoryChart = createCoinBalanceHistoryChart(coinBalanceHistoryChartElement)
}
formatAllUsdValues()
updateAllCalculatedUsdValues()
})()

@ -6,6 +6,7 @@ import humps from 'humps'
import listMorph from '../lib/list_morph' import listMorph from '../lib/list_morph'
import reduceReducers from 'reduce-reducers' import reduceReducers from 'reduce-reducers'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import '../app'
/** /**
* This is a generic lib to add pagination with asynchronous page loading. There are two ways of * This is a generic lib to add pagination with asynchronous page loading. There are two ways of
@ -271,7 +272,7 @@ export function createAsyncLoadStore (reducer, initialState, itemKey) {
}) })
} }
connectElements({store, elements}) connectElements({ store, elements })
firstPageLoad(store) firstPageLoad(store)
return store return store
} }
@ -280,20 +281,20 @@ function firstPageLoad (store) {
const $element = $('[data-async-listing]') const $element = $('[data-async-listing]')
function loadItemsNext () { function loadItemsNext () {
const path = store.getState().nextPagePath const path = store.getState().nextPagePath
store.dispatch({type: 'START_REQUEST'}) store.dispatch({ type: 'START_REQUEST' })
$.getJSON(path, {type: 'JSON'}) $.getJSON(path, { type: 'JSON' })
.done(response => store.dispatch(Object.assign({type: 'ITEMS_FETCHED'}, humps.camelizeKeys(response)))) .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response))))
.fail(() => store.dispatch({type: 'REQUEST_ERROR'})) .fail(() => store.dispatch({ type: 'REQUEST_ERROR' }))
.always(() => store.dispatch({type: 'FINISH_REQUEST'})) .always(() => store.dispatch({ type: 'FINISH_REQUEST' }))
} }
function loadItemsPrev () { function loadItemsPrev () {
const path = store.getState().prevPagePath const path = store.getState().prevPagePath
store.dispatch({type: 'START_REQUEST'}) store.dispatch({ type: 'START_REQUEST' })
$.getJSON(path, {type: 'JSON'}) $.getJSON(path, { type: 'JSON' })
.done(response => store.dispatch(Object.assign({type: 'ITEMS_FETCHED'}, humps.camelizeKeys(response)))) .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response))))
.fail(() => store.dispatch({type: 'REQUEST_ERROR'})) .fail(() => store.dispatch({ type: 'REQUEST_ERROR' }))
.always(() => store.dispatch({type: 'FINISH_REQUEST'})) .always(() => store.dispatch({ type: 'FINISH_REQUEST' }))
} }
loadItemsNext() loadItemsNext()
@ -305,14 +306,14 @@ function firstPageLoad (store) {
$element.on('click', '[data-next-page-button]', (event) => { $element.on('click', '[data-next-page-button]', (event) => {
event.preventDefault() event.preventDefault()
loadItemsNext() loadItemsNext()
store.dispatch({type: 'NAVIGATE_TO_OLDER'}) store.dispatch({ type: 'NAVIGATE_TO_OLDER' })
event.stopImmediatePropagation() event.stopImmediatePropagation()
}) })
$element.on('click', '[data-prev-page-button]', (event) => { $element.on('click', '[data-prev-page-button]', (event) => {
event.preventDefault() event.preventDefault()
loadItemsPrev() loadItemsPrev()
store.dispatch({type: 'NAVIGATE_TO_NEWER'}) store.dispatch({ type: 'NAVIGATE_TO_NEWER' })
event.stopImmediatePropagation() event.stopImmediatePropagation()
}) })
} }
@ -320,6 +321,6 @@ function firstPageLoad (store) {
const $element = $('[data-async-load]') const $element = $('[data-async-load]')
if ($element.length) { if ($element.length) {
const store = createStore(asyncReducer) const store = createStore(asyncReducer)
connectElements({store, elements}) connectElements({ store, elements })
firstPageLoad(store) firstPageLoad(store)
} }

@ -38,13 +38,13 @@ window.AwesompleteUtil = (function () {
var lv = Array.isArray(data) var lv = Array.isArray(data)
? { label: data[0], value: data[1] } ? { label: data[0], value: data[1] }
: typeof data === 'object' && 'label' in data && 'value' in data ? data : { label: data, value: data } : typeof data === 'object' && 'label' in data && 'value' in data ? data : { label: data, value: data }
return {label: lv.label || lv.value, value: lv.value} return { label: lv.label || lv.value, value: lv.value }
} }
// Helper to send events with detail property. // Helper to send events with detail property.
function _fire (target, name, detail) { function _fire (target, name, detail) {
// $.fire uses deprecated methods but other methods don't work in IE11. // $.fire uses deprecated methods but other methods don't work in IE11.
return $.fire(target, name, {detail: detail}) return $.fire(target, name, { detail: detail })
} }
// Look if there is an exact match or a mismatch, set awe-found, awe-not-found css class and send match events. // Look if there is an exact match or a mismatch, set awe-found, awe-not-found css class and send match events.
@ -74,7 +74,7 @@ window.AwesompleteUtil = (function () {
} }
} }
// Don't want to change the real input field, emulate a fake one. // Don't want to change the real input field, emulate a fake one.
fake = {input: {value: ''}} fake = { input: { value: '' } }
// Determine how this suggestion would look like if it is replaced in the input field, // Determine how this suggestion would look like if it is replaced in the input field,
// it is an exact match if somebody types exactly that. // it is an exact match if somebody types exactly that.
// Use the fake input here. fake.input.value will contain the result of the replace function. // Use the fake input here. fake.input.value will contain the result of the replace function.
@ -222,7 +222,7 @@ window.AwesompleteUtil = (function () {
awe.utilprops.url, awe.utilprops.url,
awe.utilprops.urlEnd, awe.utilprops.urlEnd,
awe.utilprops.loadall ? '' : val, awe.utilprops.loadall ? '' : val,
_onLoad.bind({awe: awe, xhr: xhr, queryVal: val}), _onLoad.bind({ awe: awe, xhr: xhr, queryVal: val }),
xhr xhr
) )
} else { } else {
@ -401,15 +401,15 @@ window.AwesompleteUtil = (function () {
// for arrays at top and subtop level // for arrays at top and subtop level
if (level < 2 && prop) { if (level < 2 && prop) {
// if a 'value' is specified and found a mathing property, create extra 'value' property. // if a 'value' is specified and found a mathing property, create extra 'value' property.
if (value && (prop + '.').lastIndexOf(value + '.', 0) === 0) { result['value'] = result[prop] } if (value && (prop + '.').lastIndexOf(value + '.', 0) === 0) { result.value = result[prop] }
// if a 'label' is specified and found a mathing property, create extra 'label' property. // if a 'label' is specified and found a mathing property, create extra 'label' property.
if (label && (prop + '.').lastIndexOf(label + '.', 0) === 0) { result['label'] = result[prop] } if (label && (prop + '.').lastIndexOf(label + '.', 0) === 0) { result.label = result[prop] }
} }
if (level === 0) { if (level === 0) {
// Make sure that both value and label properties exist, even if they are nil. // Make sure that both value and label properties exist, even if they are nil.
// This is handy with limit 0 or 1 when the result doesn't have to contain an array. // This is handy with limit 0 or 1 when the result doesn't have to contain an array.
if (value && !('value' in result)) { result['value'] = null } if (value && !('value' in result)) { result.value = null }
if (label && !('label' in result)) { result['label'] = null } if (label && !('label' in result)) { result.label = null }
} }
return result return result
} }
@ -506,17 +506,18 @@ window.AwesompleteUtil = (function () {
var boundOnKeydown = _onKeydown.bind(awe) var boundOnKeydown = _onKeydown.bind(awe)
var boundOnInput = _onInput.bind(awe) var boundOnInput = _onInput.bind(awe)
var boundSelect = _select.bind(awe) var boundSelect = _select.bind(awe)
var boundDetach = _detach.bind({awe: awe, var boundDetach = _detach.bind({
awe: awe,
boundMatch: boundMatch, boundMatch: boundMatch,
boundOnInput: boundOnInput, boundOnInput: boundOnInput,
boundOnKeydown: boundOnKeydown, boundOnKeydown: boundOnKeydown,
boundSelect: boundSelect boundSelect: boundSelect
}) })
var events = { var events = {
'keydown': boundOnKeydown, keydown: boundOnKeydown,
'input': boundOnInput input: boundOnInput
} }
events['blur'] = events[_AWE_CLOSE] = events[_AWE_LOAD] = boundMatch events.blur = events[_AWE_CLOSE] = events[_AWE_LOAD] = boundMatch
events[_AWE_SELECT] = boundSelect events[_AWE_SELECT] = boundSelect
$.bind(elem, events) $.bind(elem, events)
@ -552,7 +553,7 @@ window.AwesompleteUtil = (function () {
// Create function to copy a field from the selected autocomplete item to another DOM element. // Create function to copy a field from the selected autocomplete item to another DOM element.
// dataField can be null. // dataField can be null.
createCopyFun: function (sourceId, dataField, targetId) { createCopyFun: function (sourceId, dataField, targetId) {
return _copyFun.bind({sourceId: sourceId, dataField: dataField, targetId: $(targetId) || targetId}) return _copyFun.bind({ sourceId: sourceId, dataField: dataField, targetId: $(targetId) || targetId })
}, },
// attach copy function to event listeners. prepop is optional and by default true. // attach copy function to event listeners. prepop is optional and by default true.
@ -585,7 +586,7 @@ window.AwesompleteUtil = (function () {
// Create function for combobox button (btnId) to toggle dropdown list. // Create function for combobox button (btnId) to toggle dropdown list.
createClickFun: function (btnId, awe) { createClickFun: function (btnId, awe) {
return _clickFun.bind({btnId: btnId, awe: awe}) return _clickFun.bind({ btnId: btnId, awe: awe })
}, },
// Attach click function for combobox to click event. // Attach click function for combobox to click event.

@ -17,7 +17,7 @@ $(function () {
const siblings = $(this).siblings() const siblings = $(this).siblings()
if (siblings.is(':hidden')) { if (siblings.is(':hidden')) {
siblings.css({ 'display': 'flex' }) siblings.css({ display: 'flex' })
} else { } else {
siblings.hide() siblings.hide()
} }

@ -3,7 +3,7 @@ import $ from 'jquery'
const clipboard = new ClipboardJS('[data-clipboard-text]') const clipboard = new ClipboardJS('[data-clipboard-text]')
clipboard.on('success', ({trigger}) => { clipboard.on('success', ({ trigger }) => {
const copyButton = $(trigger) const copyButton = $(trigger)
copyButton.tooltip('dispose') copyButton.tooltip('dispose')

@ -8,7 +8,7 @@ export function createCoinBalanceHistoryChart (el) {
const $chartError = $('[data-chart-error-message]') const $chartError = $('[data-chart-error-message]')
const dataPath = el.dataset.coin_balance_history_data_path const dataPath = el.dataset.coin_balance_history_data_path
$.getJSON(dataPath, {type: 'JSON'}) $.getJSON(dataPath, { type: 'JSON' })
.done(data => { .done(data => {
$chartContainer.show() $chartContainer.show()
@ -53,7 +53,7 @@ export function createCoinBalanceHistoryChart (el) {
}, },
scaleLabel: { scaleLabel: {
display: true, display: true,
labelString: window.localized['Ether'] labelString: window.localized.Ether
} }
}] }]
} }

@ -1,8 +1,6 @@
import $ from 'jquery' import $ from 'jquery'
import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import socket from '../socket'
export function formatUsdValue (value) { export function formatUsdValue (value) {
return `${formatCurrencyValue(value)} USD` return `${formatCurrencyValue(value)} USD`
@ -45,6 +43,7 @@ export function formatAllUsdValues (root) {
formatAllUsdValues() formatAllUsdValues()
function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExchangeRate) { function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExchangeRate) {
// eslint-disable-next-line no-prototype-builtins
if (!el.dataset.hasOwnProperty('weiValue')) return if (!el.dataset.hasOwnProperty('weiValue')) return
const ether = weiToEther(el.dataset.weiValue) const ether = weiToEther(el.dataset.weiValue)
const usd = etherToUSD(ether, usdExchangeRate) const usd = etherToUSD(ether, usdExchangeRate)
@ -62,7 +61,3 @@ export function updateAllCalculatedUsdValues (usdExchangeRate) {
$('[data-usd-unit-price]').each((i, el) => tryUpdateUnitPriceValues(el, usdExchangeRate)) $('[data-usd-unit-price]').each((i, el) => tryUpdateUnitPriceValues(el, usdExchangeRate))
} }
updateAllCalculatedUsdValues() updateAllCalculatedUsdValues()
export const exchangeRateChannel = socket.channel(`exchange_rate:new_rate`)
exchangeRateChannel.join()
exchangeRateChannel.on('new_rate', (msg) => updateAllCalculatedUsdValues(humps.camelizeKeys(msg).exchangeRate.usdValue))

@ -20,6 +20,6 @@ export function updateIndexStatus (msg = {}) {
} }
updateIndexStatus() updateIndexStatus()
const indexingChannel = socket.channel(`blocks:indexing`) const indexingChannel = socket.channel('blocks:indexing')
indexingChannel.join() indexingChannel.join()
indexingChannel.on('index_status', (msg) => updateIndexStatus(humps.camelizeKeys(msg))) indexingChannel.on('index_status', (msg) => updateIndexStatus(humps.camelizeKeys(msg)))

@ -4,7 +4,6 @@ import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import { formatUsdValue } from '../lib/currency' import { formatUsdValue } from '../lib/currency'
import sassVariables from '../../css/app.scss' import sassVariables from '../../css/app.scss'
import { showLoader } from '../lib/utils'
const config = { const config = {
type: 'line', type: 'line',
@ -61,7 +60,7 @@ const config = {
mode: 'index', mode: 'index',
intersect: false, intersect: false,
callbacks: { callbacks: {
label: ({datasetIndex, yLabel}, {datasets}) => { label: ({ datasetIndex, yLabel }, { datasets }) => {
const label = datasets[datasetIndex].label const label = datasets[datasetIndex].label
if (datasets[datasetIndex].yAxisID === 'price') { if (datasets[datasetIndex].yAxisID === 'price') {
return `${label}: ${formatUsdValue(yLabel)}` return `${label}: ${formatUsdValue(yLabel)}`
@ -76,16 +75,36 @@ const config = {
} }
} }
function getDataFromLocalStorage (key) {
const data = window.localStorage.getItem(key)
return data ? JSON.parse(data) : []
}
function setDataToLocalStorage (key, data) {
window.localStorage.setItem(key, JSON.stringify(data))
}
function getPriceData (marketHistoryData) { function getPriceData (marketHistoryData) {
return marketHistoryData.map(({ date, closingPrice }) => ({x: date, y: closingPrice})) if (marketHistoryData.length === 0) {
return getDataFromLocalStorage('priceData')
}
const data = marketHistoryData.map(({ date, closingPrice }) => ({ x: date, y: closingPrice }))
setDataToLocalStorage('priceData', data)
return data
} }
function getMarketCapData (marketHistoryData, availableSupply) { function getMarketCapData (marketHistoryData, availableSupply) {
if (availableSupply !== null && typeof availableSupply === 'object') { if (marketHistoryData.length === 0) {
return marketHistoryData.map(({ date, closingPrice }) => ({x: date, y: closingPrice * availableSupply[date]})) return getDataFromLocalStorage('marketCapData')
} else {
return marketHistoryData.map(({ date, closingPrice }) => ({x: date, y: closingPrice * availableSupply}))
} }
const data = marketHistoryData.map(({ date, closingPrice }) => {
const supply = (availableSupply !== null && typeof availableSupply === 'object')
? availableSupply[date]
: availableSupply
return { x: date, y: closingPrice * supply }
})
setDataToLocalStorage('marketCapData', data)
return data
} }
// colors for light and dark theme // colors for light and dark theme
@ -102,7 +121,7 @@ if (localStorage.getItem('current-color-mode') === 'dark') {
class MarketHistoryChart { class MarketHistoryChart {
constructor (el, availableSupply, marketHistoryData) { constructor (el, availableSupply, marketHistoryData) {
this.price = { this.price = {
label: window.localized['Price'], label: window.localized.Price,
yAxisID: 'price', yAxisID: 'price',
data: getPriceData(marketHistoryData), data: getPriceData(marketHistoryData),
fill: false, fill: false,
@ -123,8 +142,18 @@ class MarketHistoryChart {
} }
this.availableSupply = availableSupply this.availableSupply = availableSupply
config.data.datasets = [this.price, this.marketCap] config.data.datasets = [this.price, this.marketCap]
const isChartLoadedKey = 'isChartLoaded'
const isChartLoaded = window.sessionStorage.getItem(isChartLoadedKey) === 'true'
if (isChartLoaded) {
config.options.animation = false
} else {
window.sessionStorage.setItem(isChartLoadedKey, true)
}
this.chart = new Chart(el, config) this.chart = new Chart(el, config)
} }
update (availableSupply, marketHistoryData) { update (availableSupply, marketHistoryData) {
this.price.data = getPriceData(marketHistoryData) this.price.data = getPriceData(marketHistoryData)
if (this.availableSupply !== null && typeof this.availableSupply === 'object') { if (this.availableSupply !== null && typeof this.availableSupply === 'object') {
@ -140,32 +169,24 @@ class MarketHistoryChart {
export function createMarketHistoryChart (el) { export function createMarketHistoryChart (el) {
const dataPath = el.dataset.market_history_chart_path const dataPath = el.dataset.market_history_chart_path
const $chartLoading = $('[data-chart-loading-message]')
const isTimeout = true
const timeoutID = showLoader(isTimeout, $chartLoading)
const $chartError = $('[data-chart-error-message]') const $chartError = $('[data-chart-error-message]')
const chart = new MarketHistoryChart(el, 0, []) const chart = new MarketHistoryChart(el, 0, [])
$.getJSON(dataPath, {type: 'JSON'}) $(el).show()
$.getJSON(dataPath, { type: 'JSON' })
.done(data => { .done(data => {
const availableSupply = JSON.parse(data.supply_data) const availableSupply = JSON.parse(data.supply_data)
const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data)) const marketHistoryData = humps.camelizeKeys(JSON.parse(data.history_data))
$(el).show()
chart.update(availableSupply, marketHistoryData) chart.update(availableSupply, marketHistoryData)
}) })
.fail(() => { .fail(() => {
$(el).hide()
$chartError.show() $chartError.show()
}) })
.always(() => {
$chartLoading.hide()
clearTimeout(timeoutID)
})
return chart return chart
} }
$('[data-chart-error-message]').on('click', _event => { $('[data-chart-error-message]').on('click', _event => {
$('[data-chart-loading-message]').show()
$('[data-chart-error-message]').hide() $('[data-chart-error-message]').hide()
createMarketHistoryChart($('[data-chart="marketHistoryChart"]')[0]) createMarketHistoryChart($('[data-chart="marketHistoryChart"]')[0])
}) })

@ -1,5 +1,4 @@
import $ from 'jquery' import $ from 'jquery'
import Chart from 'chart.js'
$(function () { $(function () {
$('.js-become-candidate').on('click', function () { $('.js-become-candidate').on('click', function () {
@ -43,34 +42,34 @@ $(function () {
}) })
function setupStakesProgress (progress, total, modal) { function setupStakesProgress (progress, total, modal) {
const stakeProgress = $(`${modal} .js-stakes-progress`) // const stakeProgress = $(`${modal} .js-stakes-progress`)
const primaryColor = $('.btn-full-primary').css('background-color') // const primaryColor = $('.btn-full-primary').css('background-color')
const backgroundColors = [ // const backgroundColors = [
primaryColor, // primaryColor,
'rgba(202, 199, 226, 0.5)' // 'rgba(202, 199, 226, 0.5)'
] // ]
const progressBackground = total - progress // const progressBackground = total - progress
// eslint-disable-next-line no-unused-vars // // eslint-disable-next-line no-unused-vars
let myChart = new Chart(stakeProgress, { // const myChart = new window.Chart(stakeProgress, {
type: 'doughnut', // type: 'doughnut',
data: { // data: {
datasets: [{ // datasets: [{
data: [progress, progressBackground], // data: [progress, progressBackground],
backgroundColor: backgroundColors, // backgroundColor: backgroundColors,
hoverBackgroundColor: backgroundColors, // hoverBackgroundColor: backgroundColors,
borderWidth: 0 // borderWidth: 0
}] // }]
}, // },
options: { // options: {
cutoutPercentage: 80, // cutoutPercentage: 80,
legend: { // legend: {
display: false // display: false
}, // },
tooltips: { // tooltips: {
enabled: false // enabled: false
} // }
} // }
}) // })
} }
}) })

@ -36,7 +36,7 @@ $(function () {
window.location = $(this).attr('network-selector-item-url') window.location = $(this).attr('network-selector-item-url')
}) })
let setNetworkTab = (currentTab) => { const setNetworkTab = (currentTab) => {
if (currentTab.hasClass('active')) return if (currentTab.hasClass('active')) return
networkSelectorTab.removeClass('active') networkSelectorTab.removeClass('active')
@ -45,31 +45,31 @@ $(function () {
$(`[network-selector-tab="${currentTab.attr('network-selector-tab-filter')}"]`).addClass('active') $(`[network-selector-tab="${currentTab.attr('network-selector-tab-filter')}"]`).addClass('active')
} }
let openNetworkSelector = () => { const openNetworkSelector = () => {
mainBody.addClass('network-selector-visible') mainBody.addClass('network-selector-visible')
networkSelectorOverlay.fadeIn(FADE_IN_DELAY) networkSelectorOverlay.fadeIn(FADE_IN_DELAY)
setNetworkSelectorVisiblePosition() setNetworkSelectorVisiblePosition()
} }
let closeNetworkSelector = () => { const closeNetworkSelector = () => {
mainBody.removeClass('network-selector-visible') mainBody.removeClass('network-selector-visible')
networkSelectorOverlay.fadeOut(FADE_IN_DELAY) networkSelectorOverlay.fadeOut(FADE_IN_DELAY)
setNetworkSelectorHiddenPosition() setNetworkSelectorHiddenPosition()
} }
let getNetworkSelectorWidth = () => { const getNetworkSelectorWidth = () => {
return parseInt(networkSelector.css('width')) || parseInt(networkSelector.css('max-width')) return parseInt(networkSelector.css('width')) || parseInt(networkSelector.css('max-width'))
} }
let setNetworkSelectorHiddenPosition = () => { const setNetworkSelectorHiddenPosition = () => {
return networkSelector.css({ 'right': `-${getNetworkSelectorWidth()}px` }) return networkSelector.css({ right: `-${getNetworkSelectorWidth()}px` })
} }
let setNetworkSelectorVisiblePosition = () => { const setNetworkSelectorVisiblePosition = () => {
return networkSelector.css({ 'right': '0' }) return networkSelector.css({ right: '0' })
} }
let init = () => { const init = () => {
setNetworkSelectorHiddenPosition() setNetworkSelectorHiddenPosition()
} }

@ -1,8 +1,8 @@
import $ from 'jquery' import $ from 'jquery'
function prettyPrint (element) { function prettyPrint (element) {
let jsonString = element.dataset.json const jsonString = element.dataset.json
let pretty = JSON.stringify(JSON.parse(jsonString), undefined, 2) const pretty = JSON.stringify(JSON.parse(jsonString), undefined, 2)
element.innerHTML = pretty element.innerHTML = pretty
} }

@ -0,0 +1,3 @@
import './read_only_functions'
import './wei_ether_converter'
import '../../app'

@ -1,4 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import '../app'
// This file adds event handlers responsible for the 'Try it out' UI in the // This file adds event handlers responsible for the 'Try it out' UI in the
// Etherscan-compatible API documentation page. // Etherscan-compatible API documentation page.

@ -1,4 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import './try_api'
function composeCurlCommand (data) { function composeCurlCommand (data) {
const url = $('[data-endpoint-url]').attr('data-endpoint-url') const url = $('[data-endpoint-url]').attr('data-endpoint-url')
@ -44,8 +45,9 @@ function parseInput (input) {
} }
} }
function dropDomain (url) { function composeRequestUrl () {
return new URL(url).pathname const url = $('[data-endpoint-url]').attr('data-endpoint-url')
return url
} }
$('button[data-try-eth-api-ui-button-type="execute"]').click(event => { $('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
@ -64,10 +66,8 @@ $('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
clickedButton.html(loadingText) clickedButton.html(loadingText)
} }
const url = $('[data-endpoint-url]').attr('data-endpoint-url')
$.ajax({ $.ajax({
url: dropDomain(url), url: composeRequestUrl(),
type: 'POST', type: 'POST',
data: JSON.stringify(formData), data: JSON.stringify(formData),
dataType: 'json', dataType: 'json',

@ -17,7 +17,7 @@ export function showLoader (isTimeout, loader) {
const timeout = setTimeout(function () { const timeout = setTimeout(function () {
loader.removeAttr('hidden') loader.removeAttr('hidden')
loader.show() loader.show()
}, 1000) }, 100)
return timeout return timeout
} else { } else {
loader.hide() loader.hide()

@ -7,6 +7,9 @@ import socket, { subscribeChannel } from '../socket'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import { updateAllCalculatedUsdValues } from '../lib/currency.js' import { updateAllCalculatedUsdValues } from '../lib/currency.js'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown' import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
import '../lib/token_balance_dropdown_search'
import '../lib/async_listing_load'
import '../app'
export const initialState = { export const initialState = {
channelDisconnected: false, channelDisconnected: false,
@ -102,7 +105,7 @@ const elements = {
}, },
'[data-selector="fetched-coin-balance-block-number"]': { '[data-selector="fetched-coin-balance-block-number"]': {
load ($el) { load ($el) {
return {fetchedCoinBalanceBlockNumber: numeral($el.text()).value()} return { fetchedCoinBalanceBlockNumber: numeral($el.text()).value() }
}, },
render ($el, state, oldState) { render ($el, state, oldState) {
if (oldState.fetchedCoinBalanceBlockNumber === state.fetchedCoinBalanceBlockNumber) return if (oldState.fetchedCoinBalanceBlockNumber === state.fetchedCoinBalanceBlockNumber) return
@ -131,7 +134,7 @@ function loadCounters (store) {
function fetchCounters () { function fetchCounters () {
$.getJSON(path) $.getJSON(path)
.done(response => store.dispatch(Object.assign({type: 'COUNTERS_FETCHED'}, humps.camelizeKeys(response)))) .done(response => store.dispatch(Object.assign({ type: 'COUNTERS_FETCHED' }, humps.camelizeKeys(response))))
} }
fetchCounters() fetchCounters()

@ -4,7 +4,7 @@ import humps from 'humps'
import socket from '../../socket' import socket from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load' import { createAsyncLoadStore } from '../../lib/async_listing_load'
import { createCoinBalanceHistoryChart } from '../../lib/coin_balance_history_chart' import '../address'
export const initialState = { export const initialState = {
channelDisconnected: false channelDisconnected: false
@ -47,7 +47,7 @@ if ($('[data-page="coin-balance-history"]').length) {
const store = createAsyncLoadStore(reducer, initialState, 'dataset.blockNumber') const store = createAsyncLoadStore(reducer, initialState, 'dataset.blockNumber')
const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash
store.dispatch({type: 'PAGE_LOAD', addressHash}) store.dispatch({ type: 'PAGE_LOAD', addressHash })
connectElements({ store, elements }) connectElements({ store, elements })
const addressChannel = socket.channel(`addresses:${addressHash}`, {}) const addressChannel = socket.channel(`addresses:${addressHash}`, {})
@ -61,9 +61,4 @@ if ($('[data-page="coin-balance-history"]').length) {
msg: humps.camelizeKeys(msg) msg: humps.camelizeKeys(msg)
}) })
}) })
const chartContainer = $('[data-chart="coinBalanceHistoryChart"]')[0]
if (chartContainer) {
createCoinBalanceHistoryChart(chartContainer)
}
} }

@ -6,6 +6,7 @@ import socket from '../../socket'
import { batchChannel } from '../../lib/utils' import { batchChannel } from '../../lib/utils'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load' import { createAsyncLoadStore } from '../../lib/async_listing_load'
import '../address'
const BATCH_THRESHOLD = 10 const BATCH_THRESHOLD = 10
@ -34,7 +35,7 @@ export function reducer (state, action) {
if (state.channelDisconnected || state.beyondPageOne) return state if (state.channelDisconnected || state.beyondPageOne) return state
const incomingInternalTransactions = action.msgs const incomingInternalTransactions = action.msgs
.filter(({toAddressHash, fromAddressHash}) => ( .filter(({ toAddressHash, fromAddressHash }) => (
!state.filter || !state.filter ||
(state.filter === 'to' && toAddressHash === state.addressHash) || (state.filter === 'to' && toAddressHash === state.addressHash) ||
(state.filter === 'from' && fromAddressHash === state.addressHash) (state.filter === 'from' && fromAddressHash === state.addressHash)
@ -81,7 +82,7 @@ if ($('[data-page="address-internal-transactions"]').length) {
const store = createAsyncLoadStore(reducer, initialState, 'dataset.key') const store = createAsyncLoadStore(reducer, initialState, 'dataset.key')
const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash
store.dispatch({type: 'PAGE_LOAD', addressHash}) store.dispatch({ type: 'PAGE_LOAD', addressHash })
connectElements({ store, elements }) connectElements({ store, elements })
const addressChannel = socket.channel(`addresses:${addressHash}`, {}) const addressChannel = socket.channel(`addresses:${addressHash}`, {})

@ -3,6 +3,7 @@ import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load' import { createAsyncLoadStore } from '../../lib/async_listing_load'
import '../address'
export const initialState = { export const initialState = {
addressHash: null, addressHash: null,
@ -16,7 +17,7 @@ export function reducer (state, action) {
return Object.assign({}, state, omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'START_SEARCH': { case 'START_SEARCH': {
return Object.assign({}, state, {pagesStack: [], isSearch: true}) return Object.assign({}, state, { pagesStack: [], isSearch: true })
} }
default: default:
return state return state
@ -63,19 +64,21 @@ if ($('[data-page="address-logs"]').length) {
store.dispatch({ store.dispatch({
type: 'PAGE_LOAD', type: 'PAGE_LOAD',
addressHash: addressHash}) addressHash: addressHash
})
$element.on('click', '[data-search-button]', (event) => { $element.on('click', '[data-search-button]', (event) => {
store.dispatch({ store.dispatch({
type: 'START_SEARCH', type: 'START_SEARCH',
addressHash: addressHash}) addressHash: addressHash
})
var topic = $('[data-search-field]').val() var topic = $('[data-search-field]').val()
var path = '/search_logs?topic=' + topic + '&address_id=' + store.getState().addressHash var path = '/search_logs?topic=' + topic + '&address_id=' + store.getState().addressHash
store.dispatch({type: 'START_REQUEST'}) store.dispatch({ type: 'START_REQUEST' })
$.getJSON(path, {type: 'JSON'}) $.getJSON(path, { type: 'JSON' })
.done(response => store.dispatch(Object.assign({type: 'ITEMS_FETCHED'}, humps.camelizeKeys(response)))) .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response))))
.fail(() => store.dispatch({type: 'REQUEST_ERROR'})) .fail(() => store.dispatch({ type: 'REQUEST_ERROR' }))
.always(() => store.dispatch({type: 'FINISH_REQUEST'})) .always(() => store.dispatch({ type: 'FINISH_REQUEST' }))
}) })
$element.on('click', '[data-cancel-search-button]', (event) => { $element.on('click', '[data-cancel-search-button]', (event) => {

@ -5,6 +5,7 @@ import humps from 'humps'
import { subscribeChannel } from '../../socket' import { subscribeChannel } from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load' import { createAsyncLoadStore } from '../../lib/async_listing_load'
import '../address'
export const initialState = { export const initialState = {
addressHash: null, addressHash: null,
@ -32,12 +33,12 @@ export function reducer (state, action) {
return state return state
} }
return Object.assign({}, state, { items: [ action.msg.transactionHtml, ...state.items ] }) return Object.assign({}, state, { items: [action.msg.transactionHtml, ...state.items] })
} }
case 'RECEIVED_NEW_REWARD': { case 'RECEIVED_NEW_REWARD': {
if (state.channelDisconnected) return state if (state.channelDisconnected) return state
return Object.assign({}, state, { items: [ action.msg.rewardHtml, ...state.items ] }) return Object.assign({}, state, { items: [action.msg.rewardHtml, ...state.items] })
} }
default: default:
return state return state

@ -4,6 +4,7 @@ import humps from 'humps'
import socket from '../../socket' import socket from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js' import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load.js' import { createAsyncLoadStore } from '../../lib/async_listing_load.js'
import '../address'
export const initialState = { export const initialState = {
addressHash: null, addressHash: null,

@ -1,4 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import '../../app'
const runTask = (event) => { const runTask = (event) => {
const element = event.currentTarget const element = event.currentTarget

@ -9,6 +9,7 @@ import humps from 'humps'
import socket from '../socket' import socket from '../socket'
import { connectElements } from '../lib/redux_helpers.js' import { connectElements } from '../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../lib/async_listing_load' import { createAsyncLoadStore } from '../lib/async_listing_load'
import '../app'
export const initialState = { export const initialState = {
channelDisconnected: false channelDisconnected: false
@ -91,7 +92,7 @@ if ($blockListPage.length || $uncleListPage.length || $reorgListPage.length) {
) )
connectElements({ store, elements }) connectElements({ store, elements })
const blocksChannel = socket.channel(`blocks:new_block`, {}) const blocksChannel = socket.channel('blocks:new_block', {})
blocksChannel.join() blocksChannel.join()
blocksChannel.onError(() => store.dispatch({ blocksChannel.onError(() => store.dispatch({
type: 'CHANNEL_DISCONNECTED' type: 'CHANNEL_DISCONNECTED'

@ -7,13 +7,14 @@ import map from 'lodash/map'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
import { exchangeRateChannel, formatUsdValue } from '../lib/currency' import { updateAllCalculatedUsdValues, formatUsdValue } from '../lib/currency'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import { batchChannel, showLoader } from '../lib/utils' import { batchChannel, showLoader } from '../lib/utils'
import listMorph from '../lib/list_morph' import listMorph from '../lib/list_morph'
import { createMarketHistoryChart } from '../lib/market_history_chart' import '../app'
const BATCH_THRESHOLD = 6 const BATCH_THRESHOLD = 6
const BLOCKS_PER_PAGE = 4
export const initialState = { export const initialState = {
addressCount: null, addressCount: null,
@ -46,11 +47,17 @@ function baseReducer (state = initialState, action) {
} }
case 'RECEIVED_NEW_BLOCK': { case 'RECEIVED_NEW_BLOCK': {
if (!state.blocks.length || state.blocks[0].blockNumber < action.msg.blockNumber) { if (!state.blocks.length || state.blocks[0].blockNumber < action.msg.blockNumber) {
let pastBlocks
if (state.blocks.length < BLOCKS_PER_PAGE) {
pastBlocks = state.blocks
} else {
pastBlocks = state.blocks.slice(0, -1)
}
return Object.assign({}, state, { return Object.assign({}, state, {
averageBlockTime: action.msg.averageBlockTime, averageBlockTime: action.msg.averageBlockTime,
blocks: [ blocks: [
action.msg, action.msg,
...state.blocks.slice(0, -1) ...pastBlocks
], ],
blockCount: action.msg.blockNumber + 1 blockCount: action.msg.blockNumber + 1
}) })
@ -89,7 +96,16 @@ function baseReducer (state = initialState, action) {
return Object.assign({}, state, { transactionCount }) return Object.assign({}, state, { transactionCount })
} }
if (!state.transactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) { const transactionsLength = state.transactions.length + action.msgs.length
if (transactionsLength < BATCH_THRESHOLD) {
return Object.assign({}, state, {
transactions: [
...action.msgs.reverse(),
...state.transactions
],
transactionCount
})
} else if (!state.transactionsBatch.length && action.msgs.length < BATCH_THRESHOLD) {
return Object.assign({}, state, { return Object.assign({}, state, {
transactions: [ transactions: [
...action.msgs.reverse(), ...action.msgs.reverse(),
@ -142,8 +158,8 @@ function withMissingBlocks (reducer) {
let chart let chart
const elements = { const elements = {
'[data-chart="marketHistoryChart"]': { '[data-chart="marketHistoryChart"]': {
load ($el) { load () {
chart = createMarketHistoryChart($el[0]) chart = window.dashboardChart
}, },
render ($el, state, oldState) { render ($el, state, oldState) {
if (!chart || (oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData) || !state.availableSupply) return if (!chart || (oldState.availableSupply === state.availableSupply && oldState.marketHistoryData === state.marketHistoryData) || !state.availableSupply) return
@ -259,26 +275,31 @@ if ($chainDetailsPage.length) {
loadBlocks(store) loadBlocks(store)
bindBlockErrorMessage(store) bindBlockErrorMessage(store)
exchangeRateChannel.on('new_rate', (msg) => store.dispatch({ const exchangeRateChannel = socket.channel('exchange_rate:new_rate')
type: 'RECEIVED_NEW_EXCHANGE_RATE', exchangeRateChannel.join()
msg: humps.camelizeKeys(msg) exchangeRateChannel.on('new_rate', (msg) => {
})) updateAllCalculatedUsdValues(humps.camelizeKeys(msg).exchangeRate.usdValue)
store.dispatch({
type: 'RECEIVED_NEW_EXCHANGE_RATE',
msg: humps.camelizeKeys(msg)
})
})
const addressesChannel = socket.channel(`addresses:new_address`) const addressesChannel = socket.channel('addresses:new_address')
addressesChannel.join() addressesChannel.join()
addressesChannel.on('count', msg => store.dispatch({ addressesChannel.on('count', msg => store.dispatch({
type: 'RECEIVED_NEW_ADDRESS_COUNT', type: 'RECEIVED_NEW_ADDRESS_COUNT',
msg: humps.camelizeKeys(msg) msg: humps.camelizeKeys(msg)
})) }))
const blocksChannel = socket.channel(`blocks:new_block`) const blocksChannel = socket.channel('blocks:new_block')
blocksChannel.join() blocksChannel.join()
blocksChannel.on('new_block', msg => store.dispatch({ blocksChannel.on('new_block', msg => store.dispatch({
type: 'RECEIVED_NEW_BLOCK', type: 'RECEIVED_NEW_BLOCK',
msg: humps.camelizeKeys(msg) msg: humps.camelizeKeys(msg)
})) }))
const transactionsChannel = socket.channel(`transactions:new_transaction`) const transactionsChannel = socket.channel('transactions:new_transaction')
transactionsChannel.join() transactionsChannel.join()
transactionsChannel.on('transaction', batchChannel((msgs) => store.dispatch({ transactionsChannel.on('transaction', batchChannel((msgs) => store.dispatch({
type: 'RECEIVED_NEW_TRANSACTION_BATCH', type: 'RECEIVED_NEW_TRANSACTION_BATCH',
@ -288,11 +309,11 @@ if ($chainDetailsPage.length) {
function loadTransactions (store) { function loadTransactions (store) {
const path = store.getState().transactionsPath const path = store.getState().transactionsPath
store.dispatch({type: 'START_TRANSACTIONS_FETCH'}) store.dispatch({ type: 'START_TRANSACTIONS_FETCH' })
$.getJSON(path) $.getJSON(path)
.done(response => store.dispatch({type: 'TRANSACTIONS_FETCHED', msg: humps.camelizeKeys(response)})) .done(response => store.dispatch({ type: 'TRANSACTIONS_FETCHED', msg: humps.camelizeKeys(response) }))
.fail(() => store.dispatch({type: 'TRANSACTIONS_FETCH_ERROR'})) .fail(() => store.dispatch({ type: 'TRANSACTIONS_FETCH_ERROR' }))
.always(() => store.dispatch({type: 'FINISH_TRANSACTIONS_FETCH'})) .always(() => store.dispatch({ type: 'FINISH_TRANSACTIONS_FETCH' }))
} }
function bindTransactionErrorMessage (store) { function bindTransactionErrorMessage (store) {
@ -325,14 +346,14 @@ export function placeHolderBlock (blockNumber) {
function loadBlocks (store) { function loadBlocks (store) {
const url = store.getState().blocksPath const url = store.getState().blocksPath
store.dispatch({type: 'START_BLOCKS_FETCH'}) store.dispatch({ type: 'START_BLOCKS_FETCH' })
$.getJSON(url) $.getJSON(url)
.done(response => { .done(response => {
store.dispatch({type: 'BLOCKS_FETCHED', msg: humps.camelizeKeys(response)}) store.dispatch({ type: 'BLOCKS_FETCHED', msg: humps.camelizeKeys(response) })
}) })
.fail(() => store.dispatch({type: 'BLOCKS_REQUEST_ERROR'})) .fail(() => store.dispatch({ type: 'BLOCKS_REQUEST_ERROR' }))
.always(() => store.dispatch({type: 'BLOCKS_FINISH_REQUEST'})) .always(() => store.dispatch({ type: 'BLOCKS_FINISH_REQUEST' }))
} }
function bindBlockErrorMessage (store) { function bindBlockErrorMessage (store) {

@ -6,6 +6,7 @@ import socket from '../socket'
import { connectElements } from '../lib/redux_helpers.js' import { connectElements } from '../lib/redux_helpers.js'
import { batchChannel } from '../lib/utils' import { batchChannel } from '../lib/utils'
import { createAsyncLoadStore } from '../lib/async_listing_load' import { createAsyncLoadStore } from '../lib/async_listing_load'
import '../app'
const BATCH_THRESHOLD = 10 const BATCH_THRESHOLD = 10
@ -102,7 +103,7 @@ if ($transactionPendingListPage.length) {
const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash') const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierHash')
connectElements({ store, elements }) connectElements({ store, elements })
const transactionsChannel = socket.channel(`transactions:new_transaction`) const transactionsChannel = socket.channel('transactions:new_transaction')
transactionsChannel.join() transactionsChannel.join()
transactionsChannel.onError(() => store.dispatch({ transactionsChannel.onError(() => store.dispatch({
type: 'CHANNEL_DISCONNECTED' type: 'CHANNEL_DISCONNECTED'
@ -118,7 +119,7 @@ if ($transactionPendingListPage.length) {
}), 1000) }), 1000)
}) })
const pendingTransactionsChannel = socket.channel(`transactions:new_pending_transaction`) const pendingTransactionsChannel = socket.channel('transactions:new_pending_transaction')
pendingTransactionsChannel.join() pendingTransactionsChannel.join()
pendingTransactionsChannel.onError(() => store.dispatch({ pendingTransactionsChannel.onError(() => store.dispatch({
type: 'CHANNEL_DISCONNECTED' type: 'CHANNEL_DISCONNECTED'

@ -0,0 +1,2 @@
import '../lib/smart_contract/index'
import './token_counters'

@ -2,6 +2,8 @@ import $ from 'jquery'
import omit from 'lodash/omit' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import '../lib/async_listing_load'
import '../app'
export const initialState = { export const initialState = {
channelDisconnected: false, channelDisconnected: false,
@ -66,13 +68,13 @@ const elements = {
function loadCounters (store) { function loadCounters (store) {
const $element = $('[data-async-counters]') const $element = $('[data-async-counters]')
const path = $element.data().asyncCounters const path = $element.data() && $element.data().asyncCounters
function fetchCounters () { function fetchCounters () {
store.dispatch({type: 'START_REQUEST'}) store.dispatch({ type: 'START_REQUEST' })
$.getJSON(path) $.getJSON(path)
.done(response => store.dispatch(Object.assign({type: 'COUNTERS_FETCHED'}, humps.camelizeKeys(response)))) .done(response => store.dispatch(Object.assign({ type: 'COUNTERS_FETCHED' }, humps.camelizeKeys(response))))
.fail(() => store.dispatch({type: 'REQUEST_ERROR'})) .fail(() => store.dispatch({ type: 'REQUEST_ERROR' }))
.always(() => store.dispatch({type: 'FINISH_REQUEST'})) .always(() => store.dispatch({ type: 'FINISH_REQUEST' }))
} }
fetchCounters() fetchCounters()

@ -4,6 +4,9 @@ import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import '../lib/transaction_input_dropdown'
import '../lib/async_listing_load'
import '../app'
export const initialState = { export const initialState = {
blockNumber: null, blockNumber: null,
@ -47,7 +50,7 @@ if ($transactionDetailsPage.length) {
const store = createStore(reducer) const store = createStore(reducer)
connectElements({ store, elements }) connectElements({ store, elements })
const blocksChannel = socket.channel(`blocks:new_block`, {}) const blocksChannel = socket.channel('blocks:new_block', {})
blocksChannel.join() blocksChannel.join()
blocksChannel.on('new_block', (msg) => store.dispatch({ blocksChannel.on('new_block', (msg) => store.dispatch({
type: 'RECEIVED_NEW_BLOCK', type: 'RECEIVED_NEW_BLOCK',

@ -6,6 +6,7 @@ import socket from '../socket'
import { connectElements } from '../lib/redux_helpers' import { connectElements } from '../lib/redux_helpers'
import { createAsyncLoadStore } from '../lib/async_listing_load' import { createAsyncLoadStore } from '../lib/async_listing_load'
import { batchChannel } from '../lib/utils' import { batchChannel } from '../lib/utils'
import '../app'
const BATCH_THRESHOLD = 10 const BATCH_THRESHOLD = 10
@ -85,7 +86,7 @@ if ($transactionListPage.length) {
connectElements({ store, elements }) connectElements({ store, elements })
const transactionsChannel = socket.channel(`transactions:new_transaction`) const transactionsChannel = socket.channel('transactions:new_transaction')
transactionsChannel.join() transactionsChannel.join()
transactionsChannel.onError(() => store.dispatch({ transactionsChannel.onError(() => store.dispatch({
type: 'CHANNEL_DISCONNECTED' type: 'CHANNEL_DISCONNECTED'

@ -4,6 +4,7 @@ import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import { subscribeChannel } from '../socket' import { subscribeChannel } from '../socket'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import '../app'
export const initialState = { export const initialState = {
channelDisconnected: false, channelDisconnected: false,
@ -67,7 +68,7 @@ const elements = {
}) })
$('.js-btn-add-contract-library').on('click', function () { $('.js-btn-add-contract-library').on('click', function () {
let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group') const nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
if (nextContractLibrary) { if (nextContractLibrary) {
nextContractLibrary.addClass('active') nextContractLibrary.addClass('active')
@ -88,6 +89,26 @@ const elements = {
const $contractVerificationPage = $('[data-page="contract-verification"]') const $contractVerificationPage = $('[data-page="contract-verification"]')
function filterNightlyBuilds (filter) {
var select, options
select = document.getElementById('smart_contract_compiler_version')
options = select.getElementsByTagName('option')
for (const option of options) {
var txtValue = option.textContent || option.innerText
if (filter) {
if (txtValue.toLowerCase().indexOf('nightly') > -1) {
option.style.display = 'none'
} else {
option.style.display = ''
}
} else {
if (txtValue.toLowerCase().indexOf('nightly') > -1) {
option.style.display = ''
}
}
}
}
if ($contractVerificationPage.length) { if ($contractVerificationPage.length) {
const store = createStore(reducer) const store = createStore(reducer)
const addressHash = $('#smart_contract_address_hash').val() const addressHash = $('#smart_contract_address_hash').val()
@ -116,11 +137,39 @@ if ($contractVerificationPage.length) {
}) })
$(function () { $(function () {
setTimeout(function () {
$('.nightly-builds-false').trigger('click')
}, 10)
$('.js-btn-add-contract-libraries').on('click', function () { $('.js-btn-add-contract-libraries').on('click', function () {
$('.js-smart-contract-libraries-wrapper').show() $('.js-smart-contract-libraries-wrapper').show()
$(this).hide() $(this).hide()
}) })
$('.autodetectfalse').on('click', function () {
if ($(this).prop('checked')) { $('.constructor-arguments').show() }
})
$('.autodetecttrue').on('click', function () {
if ($(this).prop('checked')) { $('.constructor-arguments').hide() }
})
$('.nightly-builds-true').on('click', function () {
if ($(this).prop('checked')) { filterNightlyBuilds(false) }
})
$('.nightly-builds-false').on('click', function () {
if ($(this).prop('checked')) { filterNightlyBuilds(true) }
})
$('.optimization-false').on('click', function () {
if ($(this).prop('checked')) { $('.optimization-runs').hide() }
})
$('.optimization-true').on('click', function () {
if ($(this).prop('checked')) { $('.optimization-runs').show() }
})
$('.js-smart-contract-form-reset').on('click', function () { $('.js-smart-contract-form-reset').on('click', function () {
$('.js-contract-library-form-group').removeClass('active') $('.js-contract-library-form-group').removeClass('active')
$('.js-contract-library-form-group').first().addClass('active') $('.js-contract-library-form-group').first().addClass('active')
@ -130,7 +179,7 @@ if ($contractVerificationPage.length) {
}) })
$('.js-btn-add-contract-library').on('click', function () { $('.js-btn-add-contract-library').on('click', function () {
let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group') const nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
if (nextContractLibrary) { if (nextContractLibrary) {
nextContractLibrary.addClass('active') nextContractLibrary.addClass('active')

@ -1,7 +1,12 @@
import {Socket} from 'phoenix' import { Socket } from 'phoenix'
import {locale} from './locale' import { locale } from './locale'
const socket = new Socket('/socket', {params: {locale: locale}}) let websocketRootUrl = process.env.SOCKET_ROOT
if (!websocketRootUrl || websocketRootUrl === '/') {
websocketRootUrl = ''
}
const socket = new Socket(websocketRootUrl + '/socket', { params: { locale: locale } })
socket.connect() socket.connect()
export default socket export default socket

File diff suppressed because it is too large Load Diff

@ -8,8 +8,8 @@
"author": "POA Network", "author": "POA Network",
"license": "GPL-3.0", "license": "GPL-3.0",
"engines": { "engines": {
"node": "9.x", "node": "12.x",
"npm": "5.x" "npm": "6.x"
}, },
"scripts": { "scripts": {
"deploy": "webpack --mode production", "deploy": "webpack --mode production",
@ -21,52 +21,51 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.1.0-4", "@fortawesome/fontawesome-free": "^5.1.0-4",
"awesomplete": "1.1.2", "awesomplete": "1.1.2",
"bignumber.js": "^7.2.1", "bignumber.js": "^9.0.0",
"bootstrap": "^4.1.3", "bootstrap": "^4.3.1",
"chart.js": "^2.7.2", "chart.js": "^2.9.2",
"clipboard": "^2.0.1", "clipboard": "^2.0.4",
"fg-loadcss": "^2.1.0", "highlight.js": "^9.16.2",
"highlight.js": "^9.13.1", "highlightjs-solidity": "^1.0.8",
"highlightjs-solidity": "^1.0.6",
"humps": "^2.0.1", "humps": "^2.0.1",
"jquery": "^3.4.0", "jquery": "^3.4.0",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"moment": "^2.22.1", "moment": "^2.24.0",
"nanomorph": "^5.1.3", "nanomorph": "^5.4.0",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"path-parser": "^4.1.1", "path-parser": "^4.2.0",
"phoenix": "file:../../../deps/phoenix", "phoenix": "file:../../../deps/phoenix",
"phoenix_html": "file:../../../deps/phoenix_html", "phoenix_html": "file:../../../deps/phoenix_html",
"popper.js": "^1.14.3", "popper.js": "^1.14.7",
"reduce-reducers": "^0.4.3", "reduce-reducers": "^0.4.3",
"redux": "^4.0.0", "redux": "^4.0.0",
"urijs": "^1.19.1" "urijs": "^1.19.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/polyfill": "^7.0.0-beta.46", "@babel/core": "^7.7.2",
"@babel/polyfill": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"autoprefixer": "^8.4.1", "autoprefixer": "^8.4.1",
"babel-core": "^6.26.3", "babel-loader": "^8.0.6",
"babel-loader": "^7.1.4", "copy-webpack-plugin": "^5.0.5",
"babel-preset-env": "^1.6.1",
"copy-webpack-plugin": "^4.5.1",
"css-loader": "^3.1.0", "css-loader": "^3.1.0",
"eslint": "^4.15.0", "eslint": "^6.6.0",
"eslint-config-standard": "^11.0.0-beta.0", "eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^5.2.1", "eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^3.6.0", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^3.0.1", "eslint-plugin-standard": "^4.0.1",
"file-loader": "^1.1.11", "file-loader": "^4.2.0",
"jest": "^23.2.0", "jest": "^25.1.0",
"mini-css-extract-plugin": "^0.8.0", "mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.12.0", "node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^2.1.4", "postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0", "sass-loader": "^8.0.0",
"style-loader": "^0.21.0", "style-loader": "^1.0.0",
"terser-webpack-plugin": "^1.3.0", "terser-webpack-plugin": "^2.2.1",
"webpack": "^4.6.0", "webpack": "^4.41.2",
"webpack-cli": "^3.0.8" "webpack-cli": "^3.3.10"
}, },
"jest": { "jest": {
"moduleNameMapper": { "moduleNameMapper": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 762 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@ -1 +1 @@
<svg height="30" width="30" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m0 30v-30h30v30z" fill="#202d6a"/><path d="m17.172 15 5.828 7.001h-4.344.775c-.497 0-.926-.229-1.223-.569l-.015.012-2.654-3.187h-.011a.7.7 0 0 0 -.528-.243.7.7 0 0 0 -.528.243h-.012l-2.653 3.187-.015-.012c-.297.34-.726.569-1.223.569h.774-4.344l5.829-7.001-5.829-7h3.569l.001-.001c.497 0 .926.229 1.223.569l.014-.012 2.652 3.185h.012c.13.147.315.245.53.245s.4-.098.53-.245h.011l2.652-3.185.015.012a1.618 1.618 0 0 1 1.224-.568h3.569z" fill="#15f9bb"/></g></svg> <svg height="30" width="30" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="m30 14.999v15.001h-30v-30h30z" fill="#12ac97"/><path d="m17.172 14.999 5.828 7h-4.344v-.001h.775c-.497 0-.926-.228-1.223-.568l-.015.012-2.655-3.188h-.019a.681.681 0 0 0 -.519-.246.681.681 0 0 0 -.519.246h-.019l-2.655 3.188-.015-.012c-.297.34-.726.568-1.223.568h.775v.001h-4.344l5.828-7-5.828-7h3.569c.497 0 .926.228 1.223.568l.015-.011 2.651 3.184h.019a.682.682 0 0 0 .523.252.682.682 0 0 0 .523-.252h.018l2.652-3.184.015.011c.297-.34.726-.568 1.223-.568h3.569z" fill="#fff"/></g></svg>

Before

Width:  |  Height:  |  Size: 555 B

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

@ -1,10 +1,11 @@
const path = require('path'); const path = require('path')
const TerserJSPlugin = require('terser-webpack-plugin'); const TerserJSPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin')
const { ContextReplacementPlugin } = require('webpack'); const { ContextReplacementPlugin } = require('webpack')
const glob = require("glob"); const glob = require('glob')
const webpack = require('webpack')
function transpileViewScript(file) { function transpileViewScript(file) {
return { return {
@ -21,7 +22,7 @@ function transpileViewScript(file) {
use: { use: {
loader: 'babel-loader' loader: 'babel-loader'
} }
}, }
] ]
} }
} }
@ -30,7 +31,7 @@ function transpileViewScript(file) {
const jsOptimizationParams = { const jsOptimizationParams = {
cache: true, cache: true,
parallel: true, parallel: true,
sourceMap: true, sourceMap: true
} }
const awesompleteJs = { const awesompleteJs = {
@ -51,19 +52,19 @@ const awesompleteJs = {
{ {
loader: "css-loader", loader: "css-loader",
} }
], ]
}, }
], ]
}, },
optimization: { optimization: {
minimizer: [ minimizer: [
new TerserJSPlugin(jsOptimizationParams), new TerserJSPlugin(jsOptimizationParams),
], ]
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: '../css/awesomplete.css' filename: '../css/awesomplete.css'
}), })
] ]
} }
@ -72,7 +73,28 @@ const appJs =
entry: { entry: {
app: './js/app.js', app: './js/app.js',
stakes: './js/pages/stakes.js', stakes: './js/pages/stakes.js',
'non-critical': './css/non-critical.scss', 'chart-loader': './js/chart-loader.js',
'chain': './js/pages/chain.js',
'blocks': './js/pages/blocks.js',
'address': './js/pages/address.js',
'address-transactions': './js/pages/address/transactions.js',
'address-coin-balances': './js/pages/address/coin_balances.js',
'address-internal-transactions': './js/pages/address/internal_transactions.js',
'address-logs': './js/pages/address/logs.js',
'address-validations': './js/pages/address/validations.js',
'validated-transactions': './js/pages/transactions.js',
'pending-transactions': './js/pages/pending_transactions.js',
'transaction': './js/pages/transaction.js',
'verification-form': './js/pages/verification_form.js',
'token-counters': './js/pages/token_counters.js',
'admin-tasks': './js/pages/admin/tasks.js',
'read-token-contract': './js/pages/read_token_contract.js',
'smart-contract-helpers': './js/lib/smart_contract/index.js',
'token-transfers-toggle': './js/lib/token_transfers_toggle.js',
'try-api': './js/lib/try_api.js',
'try-eth-api': './js/lib/try_eth_api.js',
'async-listing-load': './js/lib/async_listing_load',
'non-critical': './css/non-critical.scss'
}, },
output: { output: {
filename: '[name].js', filename: '[name].js',
@ -95,17 +117,19 @@ const appJs =
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
{ {
loader: "css-loader" loader: 'css-loader'
}, { }, {
loader: "postcss-loader" loader: 'postcss-loader'
}, { }, {
loader: "sass-loader", loader: 'sass-loader',
options: { options: {
precision: 8, sassOptions: {
includePaths: [ precision: 8,
'node_modules/bootstrap/scss', includePaths: [
'node_modules/@fortawesome/fontawesome-free/scss' 'node_modules/bootstrap/scss',
] 'node_modules/@fortawesome/fontawesome-free/scss'
]
}
} }
} }
] ]
@ -127,10 +151,13 @@ const appJs =
filename: '../css/[name].css' filename: '../css/[name].css'
}), }),
new CopyWebpackPlugin([{ from: 'static/', to: '../' }]), new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/) new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
new webpack.DefinePlugin({
'process.env.SOCKET_ROOT': JSON.stringify(process.env.SOCKET_ROOT)
})
] ]
} }
const viewScripts = glob.sync('./js/view_specific/**/*.js').map(transpileViewScript); const viewScripts = glob.sync('./js/view_specific/**/*.js').map(transpileViewScript)
module.exports = viewScripts.concat(appJs, awesompleteJs); module.exports = viewScripts.concat(appJs, awesompleteJs)

@ -12,7 +12,8 @@ config :block_scout_web,
version: System.get_env("BLOCKSCOUT_VERSION"), version: System.get_env("BLOCKSCOUT_VERSION"),
release_link: System.get_env("RELEASE_LINK"), release_link: System.get_env("RELEASE_LINK"),
decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN"), decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN"),
show_percentage: if(System.get_env("SHOW_ADDRESS_MARKETCAP_PERCENTAGE", "true") == "false", do: false, else: true) show_percentage: if(System.get_env("SHOW_ADDRESS_MARKETCAP_PERCENTAGE", "true") == "false", do: false, else: true),
checksum_address_hashes: if(System.get_env("CHECKSUM_ADDRESS_HASHES", "true") == "false", do: false, else: true)
config :block_scout_web, BlockScoutWeb.Chain, config :block_scout_web, BlockScoutWeb.Chain,
network: System.get_env("NETWORK"), network: System.get_env("NETWORK"),
@ -41,7 +42,8 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
url: [ url: [
scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "http", scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "http",
host: System.get_env("BLOCKSCOUT_HOST") || "localhost", host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/" path: System.get_env("NETWORK_PATH") || "/",
api_path: System.get_env("API_PATH") || "/"
], ],
render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: BlockScoutWeb.PubSub] pubsub: [name: BlockScoutWeb.PubSub]

@ -15,13 +15,16 @@ port =
end end
config :block_scout_web, BlockScoutWeb.Endpoint, config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base:
System.get_env("SECRET_KEY_BASE") || "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5",
http: [ http: [
port: port || 4000 port: port || 4000
], ],
url: [ url: [
scheme: "http", scheme: "http",
host: System.get_env("BLOCKSCOUT_HOST") || "localhost", host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/" path: System.get_env("NETWORK_PATH") || "/",
api_path: System.get_env("API_PATH") || "/"
], ],
https: [ https: [
port: (port && port + 1) || 4001, port: (port && port + 1) || 4001,
@ -78,5 +81,3 @@ config :logger, :block_scout_web,
# Set a higher stacktrace during development. Avoid configuring such # Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive. # in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20 config :phoenix, :stacktrace_depth, 20
import_config "dev.secret.exs"

@ -1,5 +0,0 @@
use Mix.Config
# Configures the endpoint
config :block_scout_web, BlockScoutWeb.Endpoint,
secret_key_base: "RMgI4C1HSkxsEjdhtGMfwAHfyT6CKWXOgzCboJflfSm4jeAlic52io05KB6mqzc5"

@ -23,7 +23,8 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "https", scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "https",
port: System.get_env("PORT"), port: System.get_env("PORT"),
host: System.get_env("BLOCKSCOUT_HOST") || "localhost", host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/" path: System.get_env("NETWORK_PATH") || "/",
api_path: System.get_env("API_PATH") || "/"
] ]
config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true

@ -8,7 +8,8 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
http: [port: 4002], http: [port: 4002],
secret_key_base: "27Swe6KtEtmN37WyEYRjKWyxYULNtrxlkCEKur4qoV+Lwtk8lafsR16ifz1XBBYj", secret_key_base: "27Swe6KtEtmN37WyEYRjKWyxYULNtrxlkCEKur4qoV+Lwtk8lafsR16ifz1XBBYj",
server: true, server: true,
pubsub: [name: BlockScoutWeb.PubSub] pubsub: [name: BlockScoutWeb.PubSub],
checksum_address_hashes: true
config :block_scout_web, BlockScoutWeb.Tracer, disabled?: false config :block_scout_web, BlockScoutWeb.Tracer, disabled?: false

@ -127,6 +127,7 @@ defmodule BlockScoutWeb.AddressChannel do
View.render_to_string( View.render_to_string(
TransactionView, TransactionView,
"_tile.html", "_tile.html",
conn: socket,
current_address: address, current_address: address,
transaction: transaction transaction: transaction
) )

@ -29,7 +29,8 @@ defmodule BlockScoutWeb.TransactionChannel do
View.render_to_string( View.render_to_string(
TransactionView, TransactionView,
"_tile.html", "_tile.html",
transaction: transaction transaction: transaction,
conn: socket
) )
push(socket, "pending_transaction", %{ push(socket, "pending_transaction", %{
@ -47,7 +48,8 @@ defmodule BlockScoutWeb.TransactionChannel do
View.render_to_string( View.render_to_string(
TransactionView, TransactionView,
"_tile.html", "_tile.html",
transaction: transaction transaction: transaction,
conn: socket
) )
push(socket, "transaction", %{ push(socket, "transaction", %{

@ -0,0 +1,57 @@
defmodule BlockScoutWeb.ChecksumAddress do
@moduledoc """
Adds checksummed version of address hashes.
"""
import Plug.Conn
alias Explorer.Chain
alias Explorer.Chain.Address
alias Phoenix.Controller
alias Plug.Conn
def init(opts), do: opts
def call(%Conn{params: %{"id" => id}} = conn, _opts) do
check_checksum(conn, id, "id")
end
def call(%Conn{params: %{"address_id" => id}} = conn, _opts) do
check_checksum(conn, id, "address_id")
end
def call(conn, _), do: conn
defp check_checksum(conn, id, param_name) do
if Application.get_env(:block_scout_web, :checksum_address_hashes) do
case Chain.string_to_address_hash(id) do
{:ok, address_hash} ->
checksummed_hash = Address.checksum(address_hash)
if checksummed_hash != id do
conn = %{conn | params: Map.merge(conn.params, %{param_name => checksummed_hash})}
path_with_checksummed_address = String.replace(conn.request_path, id, checksummed_hash)
new_path =
if conn.query_string != "" do
path_with_checksummed_address <> "?" <> conn.query_string
else
path_with_checksummed_address
end
conn
|> Controller.redirect(to: new_path)
|> halt
else
conn
end
_ ->
conn
end
else
conn
end
end
end

@ -9,6 +9,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
alias BlockScoutWeb.AddressCoinBalanceView alias BlockScoutWeb.AddressCoinBalanceView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View alias Phoenix.View
@ -64,7 +65,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: current_path(conn), current_path: current_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => to_string(address_hash)}) counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
:error -> :error ->

@ -57,9 +57,12 @@ defmodule BlockScoutWeb.AddressController do
end end
def index(conn, _params) do def index(conn, _params) do
total_supply = Chain.total_supply()
render(conn, "index.html", render(conn, "index.html",
current_path: current_path(conn), current_path: current_path(conn),
address_count: Chain.count_addresses_from_cache() address_count: Chain.address_estimated_count(),
total_supply: total_supply
) )
end end

@ -9,6 +9,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
@ -32,7 +33,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
address: address, address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => to_string(address_hash)}) counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
_ -> _ ->

@ -5,6 +5,7 @@ defmodule BlockScoutWeb.AddressTokenController do
alias BlockScoutWeb.AddressTokenView alias BlockScoutWeb.AddressTokenView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View alias Phoenix.View
@ -63,7 +64,7 @@ defmodule BlockScoutWeb.AddressTokenController do
current_path: current_path(conn), current_path: current_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
counters_path: address_path(conn, :address_counters, %{"id" => to_string(address_hash)}) counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
:error -> :error ->

@ -4,11 +4,25 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
alias BlockScoutWeb.TransactionView alias BlockScoutWeb.TransactionView
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, import BlockScoutWeb.Chain,
only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1] only: [current_filter: 1, next_page_params: 3, paging_options: 1, split_list_by_page: 1]
@transaction_necessity_by_association [
necessity_by_association: %{
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
[token_transfers: :token_contract_address] => :optional,
:block => :required
}
]
def index( def index(
conn, conn,
@ -83,7 +97,89 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: current_path(conn), current_path: current_path(conn),
token: token, token: token,
counters_path: address_path(conn, :address_counters, %{"id" => to_string(address_hash)}) counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
)
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
def index(
conn,
%{
"address_id" => address_hash_string,
"type" => "JSON"
} = params
) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
options =
@transaction_necessity_by_association
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
transactions =
Chain.address_hash_to_token_transfers(
address_hash,
options
)
{transactions_paginated, next_page} = split_list_by_page(transactions)
next_page_path =
case next_page_params(next_page, transactions_paginated, params) do
nil ->
nil
next_page_params ->
address_token_transfers_path(
conn,
:index,
address_hash_string,
Map.delete(next_page_params, "type")
)
end
transfers_json =
Enum.map(transactions_paginated, fn transaction ->
View.render_to_string(
TransactionView,
"_tile.html",
conn: conn,
transaction: transaction,
current_address: address
)
end)
json(conn, %{items: transfers_json, next_page_path: next_page_path})
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
def index(
conn,
%{"address_id" => address_hash_string} = params
) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
current_path: current_path(conn),
counters_path: address_path(conn, :address_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
:error -> :error ->

@ -19,10 +19,6 @@ defmodule BlockScoutWeb.AddressTransactionController do
[created_contract_address: :names] => :optional, [created_contract_address: :names] => :optional,
[from_address: :names] => :optional, [from_address: :names] => :optional,
[to_address: :names] => :optional, [to_address: :names] => :optional,
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
[token_transfers: :token_contract_address] => :optional,
:block => :required :block => :required
} }
] ]

@ -1,7 +1,10 @@
defmodule BlockScoutWeb.API.RPC.StatsController do defmodule BlockScoutWeb.API.RPC.StatsController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
use Explorer.Schema
alias Explorer.{Chain, ExchangeRates} alias Explorer.{Chain, ExchangeRates}
alias Explorer.Chain.Cache.{AddressSum, AddressSumMinusBurnt}
alias Explorer.Chain.Wei alias Explorer.Chain.Wei
def tokensupply(conn, params) do def tokensupply(conn, params) do
@ -21,7 +24,7 @@ defmodule BlockScoutWeb.API.RPC.StatsController do
end end
end end
def ethsupply(conn, _params) do def ethsupplyexchange(conn, _params) do
wei_total_supply = wei_total_supply =
Chain.total_supply() Chain.total_supply()
|> Decimal.new() |> Decimal.new()
@ -29,7 +32,23 @@ defmodule BlockScoutWeb.API.RPC.StatsController do
|> Wei.to(:wei) |> Wei.to(:wei)
|> Decimal.to_string() |> Decimal.to_string()
render(conn, "ethsupply.json", total_supply: wei_total_supply) render(conn, "ethsupplyexchange.json", total_supply: wei_total_supply)
end
def ethsupply(conn, _params) do
cached_wei_total_supply = AddressSum.get_sum()
render(conn, "ethsupply.json", total_supply: cached_wei_total_supply)
end
def coinsupply(conn, _params) do
cached_coin_total_supply_wei = AddressSumMinusBurnt.get_sum_minus_burnt()
cached_coin_total_supply =
%Wei{value: Decimal.new(cached_coin_total_supply_wei)}
|> Wei.to(:ether)
render(conn, "coinsupply.json", cached_coin_total_supply)
end end
def ethprice(conn, _params) do def ethprice(conn, _params) do

@ -13,6 +13,7 @@ defmodule BlockScoutWeb.ChainController do
def show(conn, _params) do def show(conn, _params) do
transaction_estimated_count = Chain.transaction_estimated_count() transaction_estimated_count = Chain.transaction_estimated_count()
block_count = Chain.block_estimated_count() block_count = Chain.block_estimated_count()
address_count = Chain.address_estimated_count()
market_cap_calculation = market_cap_calculation =
case Application.get_env(:explorer, :supply) do case Application.get_env(:explorer, :supply) do
@ -28,7 +29,7 @@ defmodule BlockScoutWeb.ChainController do
render( render(
conn, conn,
"show.html", "show.html",
address_count: Chain.count_addresses_from_cache(), address_count: address_count,
average_block_time: AverageBlockTime.average_block_time(), average_block_time: AverageBlockTime.average_block_time(),
exchange_rate: exchange_rate, exchange_rate: exchange_rate,
chart_data_path: market_history_chart_path(conn, :show), chart_data_path: market_history_chart_path(conn, :show),

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.HolderController do
alias BlockScoutWeb.Tokens.HolderView alias BlockScoutWeb.Tokens.HolderView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, import BlockScoutWeb.Chain,
@ -52,7 +53,7 @@ defmodule BlockScoutWeb.Tokens.HolderController do
"index.html", "index.html",
current_path: current_path(conn), current_path: current_path(conn),
token: Market.add_price(token), token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)}) counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
:error -> :error ->

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
alias BlockScoutWeb.Tokens.TransferView alias BlockScoutWeb.Tokens.TransferView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
@ -24,7 +25,7 @@ defmodule BlockScoutWeb.Tokens.Instance.TransferController do
conn, conn,
:index, :index,
token_id, token_id,
token.contract_address_hash, Address.checksum(token.contract_address_hash),
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
alias BlockScoutWeb.Tokens.InventoryView alias BlockScoutWeb.Tokens.InventoryView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.TokenTransfer alias Explorer.Chain.{Address, TokenTransfer}
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0] import BlockScoutWeb.Chain, only: [split_list_by_page: 1, default_paging_options: 0]
@ -28,7 +28,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
token_inventory_path( token_inventory_path(
conn, conn,
:index, :index,
address_hash_string, Address.checksum(address_hash_string),
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end
@ -71,7 +71,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
"index.html", "index.html",
current_path: current_path(conn), current_path: current_path(conn),
token: Market.add_price(token), token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)}) counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
:error -> :error ->

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
def index(conn, %{"token_id" => address_hash_string}) do def index(conn, %{"token_id" => address_hash_string}) do
options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}] options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
@ -13,7 +14,7 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
conn, conn,
"index.html", "index.html",
token: Market.add_price(token), token: Market.add_price(token),
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)}) counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)})
) )
else else
:not_found -> :not_found ->

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.Tokens.TransferController do
alias BlockScoutWeb.Tokens.TransferView alias BlockScoutWeb.Tokens.TransferView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address
alias Phoenix.View alias Phoenix.View
import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3] import BlockScoutWeb.Chain, only: [split_list_by_page: 1, paging_options: 1, next_page_params: 3]
@ -19,7 +20,12 @@ defmodule BlockScoutWeb.Tokens.TransferController do
nil nil
next_page_params -> next_page_params ->
token_transfer_path(conn, :index, token.contract_address_hash, Map.delete(next_page_params, "type")) token_transfer_path(
conn,
:index,
Address.checksum(token.contract_address_hash),
Map.delete(next_page_params, "type")
)
end end
transfers_json = transfers_json =
@ -51,7 +57,7 @@ defmodule BlockScoutWeb.Tokens.TransferController do
render( render(
conn, conn,
"index.html", "index.html",
counters_path: token_path(conn, :token_counters, %{"id" => to_string(address_hash)}), counters_path: token_path(conn, :token_counters, %{"id" => Address.checksum(address_hash)}),
current_path: current_path(conn), current_path: current_path(conn),
token: Market.add_price(token) token: Market.add_price(token)
) )

@ -2,7 +2,10 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.TransactionView alias BlockScoutWeb.TransactionView
alias EthereumJSONRPC
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Import
alias Explorer.Chain.Import.Runner.InternalTransactions
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
def index(conn, %{"transaction_id" => hash_string}) do def index(conn, %{"transaction_id" => hash_string}) do
@ -19,7 +22,49 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
:token_transfers => :optional :token_transfers => :optional
} }
) do ) do
internal_transactions = Chain.transaction_to_internal_transactions(hash) internal_transactions = Chain.all_transaction_to_internal_transactions(hash)
first_trace_exists =
Enum.find_index(internal_transactions, fn trace ->
trace.index == 0
end)
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
internal_transactions =
if first_trace_exists do
internal_transactions
else
response =
Chain.fetch_first_trace(
[
%{
block_hash: transaction.block_hash,
block_number: transaction.block_number,
hash_data: hash_string,
transaction_index: transaction.index
}
],
json_rpc_named_arguments
)
case response do
{:ok, first_trace_params} ->
InternalTransactions.run_insert_only(first_trace_params, %{
timeout: :infinity,
timestamps: Import.timestamps(),
internal_transactions: %{params: first_trace_params}
})
Chain.all_transaction_to_internal_transactions(hash)
{:error, _} ->
internal_transactions
:ignore ->
internal_transactions
end
end
render( render(
conn, conn,

@ -27,14 +27,11 @@ defmodule BlockScoutWeb.Endpoint do
android-chrome-512x512.png android-chrome-512x512.png
apple-touch-icon.png apple-touch-icon.png
browserconfig.xml browserconfig.xml
favicon.ico
favicon-16x16.png
favicon-32x32.png
mstile-150x150.png mstile-150x150.png
safari-pinned-tab.svg safari-pinned-tab.svg
site.manifest
robots.txt robots.txt
) ),
only_matching: ~w(manifest)
) )
# Code reloading can be explicitly enabled under the # Code reloading can be explicitly enabled under the

@ -261,12 +261,20 @@ defmodule BlockScoutWeb.Etherscan do
"result" => "21265524714464" "result" => "21265524714464"
} }
@stats_ethsupplyexchange_example_value %{
"status" => "1",
"message" => "OK",
"result" => "101959776311500000000000000"
}
@stats_ethsupply_example_value %{ @stats_ethsupply_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
"result" => "101959776311500000000000000" "result" => "101959776311500000000000000"
} }
@stats_coinsupply_example_value 101_959_776.3115
@stats_ethprice_example_value %{ @stats_ethprice_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -299,7 +307,7 @@ defmodule BlockScoutWeb.Etherscan do
@block_eth_block_number_example_value %{ @block_eth_block_number_example_value %{
"jsonrpc" => "2.0", "jsonrpc" => "2.0",
"result" => "767969", "result" => "0xb33bf1",
"id" => 1 "id" => 1
} }
@ -583,6 +591,12 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("Some Token Name") example: ~s("Some Token Name")
} }
@token_id_type %{
type: "integer",
definition: "id of token",
example: ~s("0")
}
@token_symbol_type %{ @token_symbol_type %{
type: "string", type: "string",
definition: "Trading symbol of the token.", definition: "Trading symbol of the token.",
@ -746,6 +760,7 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("663046792267785498951364") example: ~s("663046792267785498951364")
}, },
tokenName: @token_name_type, tokenName: @token_name_type,
tokenID: @token_id_type,
tokenSymbol: @token_symbol_type, tokenSymbol: @token_symbol_type,
tokenDecimal: @token_decimal_type, tokenDecimal: @token_decimal_type,
transactionIndex: @transaction_index_type, transactionIndex: @transaction_index_type,
@ -1772,9 +1787,35 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@stats_ethsupplyexchange_action %{
name: "ethsupplyexchange",
description: "Get total supply in Wei from exchange.",
required_params: [],
optional_params: [],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@stats_ethsupplyexchange_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "integer",
description: "The total supply.",
example: ~s("101959776311500000000000000")
}
}
}
}
]
}
@stats_ethsupply_action %{ @stats_ethsupply_action %{
name: "ethsupply", name: "ethsupply",
description: "Get total supply in Wei.", description: "Get total supply in Wei from DB.",
required_params: [], required_params: [],
optional_params: [], optional_params: [],
responses: [ responses: [
@ -1789,7 +1830,7 @@ defmodule BlockScoutWeb.Etherscan do
message: @message_type, message: @message_type,
result: %{ result: %{
type: "integer", type: "integer",
description: "The total supply.", description: "The total supply in Wei from DB.",
example: ~s("101959776311500000000000000") example: ~s("101959776311500000000000000")
} }
} }
@ -1798,6 +1839,30 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@stats_coinsupply_action %{
name: "coinsupply",
description: "Get total coin supply from DB minus burnt number.",
required_params: [],
optional_params: [],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@stats_coinsupply_example_value),
model: %{
name: "Result",
fields: %{
result: %{
type: "integer",
description: "The total supply from DB minus burnt number in coin dimension.",
example: 101_959_776.3115
}
}
}
}
]
}
@stats_ethprice_action %{ @stats_ethprice_action %{
name: "ethprice", name: "ethprice",
description: "Get latest price in USD and BTC.", description: "Get latest price in USD and BTC.",
@ -1946,7 +2011,24 @@ defmodule BlockScoutWeb.Etherscan do
@contract_verify_action %{ @contract_verify_action %{
name: "verify", name: "verify",
description: "Verify a contract with its source code and contract creation information.", description: """
Verify a contract with its source code and contract creation information.
<br/>
<br/>
<p class="api-doc-list-item-text">curl POST example:</p>
<br/>
<div class='tab-content'>
<div class='tab-pane fade show active'>
<div class="tile tile-muted p-1">
<div class="m-2">
curl -d '{"addressHash":"0xd6984e092b51337032cf0300c7291e4839be37e1","compilerVersion":"v0.5.4+commit.9549d8ff",
"contractSourceCode":"pragma solidity ^0.5.4;\n","name":"Test","optimization":false}'
-H "Content-Type: application/json" -X POST "https://blockscout.com/eth/kovan/api?module=contract&action=verify"
</pre>
</div>
</div>
</div>
""",
required_params: [ required_params: [
%{ %{
key: "addressHash", key: "addressHash",
@ -2285,7 +2367,9 @@ defmodule BlockScoutWeb.Etherscan do
name: "stats", name: "stats",
actions: [ actions: [
@stats_tokensupply_action, @stats_tokensupply_action,
@stats_ethsupplyexchange_action,
@stats_ethsupply_action, @stats_ethsupply_action,
@stats_coinsupply_action,
@stats_ethprice_action @stats_ethprice_action
] ]
} }

@ -14,7 +14,7 @@ defmodule BlockScoutWeb.Notifier do
alias Phoenix.View alias Phoenix.View
def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do
Endpoint.broadcast("addresses:new_address", "count", %{count: Chain.count_addresses_from_cache()}) Endpoint.broadcast("addresses:new_address", "count", %{count: Chain.address_estimated_count()})
addresses addresses
|> Stream.reject(fn %Address{fetched_coin_balance: fetched_coin_balance} -> is_nil(fetched_coin_balance) end) |> Stream.reject(fn %Address{fetched_coin_balance: fetched_coin_balance} -> is_nil(fetched_coin_balance) end)
@ -95,7 +95,7 @@ defmodule BlockScoutWeb.Notifier do
def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do
internal_transactions internal_transactions
|> Stream.map( |> Stream.map(
&(InternalTransaction &(InternalTransaction.where_nonpending_block()
|> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index) |> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index)
|> Repo.preload([:from_address, :to_address, transaction: :block])) |> Repo.preload([:from_address, :to_address, transaction: :block]))
) )

@ -54,10 +54,22 @@ defmodule BlockScoutWeb.Router do
get("/eth_rpc_api_docs", APIDocsController, :eth_rpc) get("/eth_rpc_api_docs", APIDocsController, :eth_rpc)
end end
scope "/verify_smart_contract" do url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
pipe_through(:api) api_path = url_params[:api_path]
path = url_params[:path]
post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create) if path != api_path do
scope to_string(api_path) <> "/verify_smart_contract" do
pipe_through(:api)
post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create)
end
else
scope "/verify_smart_contract" do
pipe_through(:api)
post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create)
end
end end
if Application.get_env(:block_scout_web, WebRouter)[:enabled] do if Application.get_env(:block_scout_web, WebRouter)[:enabled] do

@ -3,7 +3,11 @@
<%= if @use_custom_tooltip == true do %> <%= if @use_custom_tooltip == true do %>
<span><%= name %> (<%= short_hash(@address) %>...)</span> <span><%= name %> (<%= short_hash(@address) %>...)</span>
<% else %> <% else %>
<span data-toggle="tooltip" data-placement="top" title="<%= @address %>"><%= name %> (<%= short_hash(@address) %>...)</span> <span data-toggle="tooltip" data-placement="top" title="<%= @address %>">
<span class="d-none d-md-none d-lg-inline d-xl-inline"><%= short_contract_name(name, 30) %></span>
<span class="d-inline d-md-inline d-lg-none d-xl-none"><%= short_contract_name(name, 10) %></span>
<span> (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>)</span>
</span>
<% end %> <% end %>
<% else %> <% else %>
<%= if assigns[:truncate] do %> <%= if assigns[:truncate] do %>

@ -4,6 +4,12 @@
class: "card-tab #{tab_status("transactions", @conn.request_path)}", class: "card-tab #{tab_status("transactions", @conn.request_path)}",
to: address_transaction_path(@conn, :index, @address.hash) to: address_transaction_path(@conn, :index, @address.hash)
) %> ) %>
<%= link(
gettext("Token Transfers"),
class: "card-tab #{tab_status("token_transfers", @conn.request_path)}",
"data-test": "token_transfers_tab_link",
to: address_token_transfers_path(@conn, :index, @address.hash)
) %>
<%= link( <%= link(
gettext("Tokens"), gettext("Tokens"),
class: "card-tab #{tab_status("tokens", @conn.request_path)}", class: "card-tab #{tab_status("tokens", @conn.request_path)}",

@ -18,12 +18,10 @@
<% end %> <% end %>
</span> </span>
</td> </td>
<%= if balance_percentage_enabled?() do %> <%= if balance_percentage_enabled?(@total_supply) do %>
<td class="stakes-td color-lighten"> <td class="stakes-td color-lighten">
<!-- percentage of coins from total supply --> <!-- percentage of coins from total supply -->
<%= if @total_supply do %> <%= balance_percentage(@address, @total_supply) %>
<%= balance_percentage(@address, @total_supply) %>
<% end %>
</td> </td>
<% end %> <% end %>
<td class="stakes-td"> <td class="stakes-td">

@ -25,7 +25,7 @@
Balance Balance
</div> </div>
</th> </th>
<%= if balance_percentage_enabled?() do %> <%= if balance_percentage_enabled?(@total_supply) do %>
<th class="stakes-table-th"> <th class="stakes-table-th">
<div class="stakes-table-th-content"> <div class="stakes-table-th-content">
Percentage Percentage
@ -40,7 +40,7 @@
</tr> </tr>
</thead> </thead>
<tbody data-items data-selector="top-addresses-list"> <tbody data-items data-selector="top-addresses-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html" %> <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", total_supply: @total_supply %>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -49,4 +49,5 @@
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div> </div>
</div> </div>
<script defer data-cfasync="false" src="<%= static_path(@conn, "/js/async-listing-load.js") %>"></script>
</section> </section>

@ -62,7 +62,9 @@
<h3 class="address-detail-hash-title <%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3> <h3 class="address-detail-hash-title <%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
<div class="d-flex flex-column flex-lg-row justify-content-start text-muted"> <div class="d-flex flex-column flex-lg-row justify-content-start text-muted">
<%= if address_name = primary_name(@address) do %> <%= if address_name = primary_name(@address) do %>
<strong class="mr-4 mb-2 text-dark"><%= address_name %></strong> <strong class="mr-4 mb-2 text-dark" title="<%= address_name %>">
<%= short_contract_name(address_name, 30) %>
</strong>
<% end %> <% end %>
<%= if @address.token do %> <%= if @address.token do %>
<span class="mr-4 mb-2"> <span class="mr-4 mb-2">

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save