merge master

pull/2404/head
slightlycyborg 5 years ago
commit 7b14b1c17a
  1. 38
      .circleci/config.yml
  2. 8
      .dialyzer-ignore
  3. 2
      .tool-versions
  4. 50
      CHANGELOG.md
  5. 1
      README.md
  6. 1
      apps/block_scout_web/assets/__tests__/pages/pending_transactions.js
  7. 3
      apps/block_scout_web/assets/css/app.scss
  8. 4
      apps/block_scout_web/assets/css/components/_footer.scss
  9. 6
      apps/block_scout_web/assets/css/components/_navbar.scss
  10. 10
      apps/block_scout_web/assets/css/components/_network-selector.scss
  11. 37
      apps/block_scout_web/assets/css/components/_stakes_table.scss
  12. 217
      apps/block_scout_web/assets/css/components/_tile.scss
  13. 3
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  14. 5
      apps/block_scout_web/assets/css/theme/_dai_variables.scss
  15. 470
      apps/block_scout_web/assets/css/theme/_dark-theme.scss
  16. 5
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss
  17. 14
      apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
  18. 5
      apps/block_scout_web/assets/css/theme/_goerli_variables.scss
  19. 5
      apps/block_scout_web/assets/css/theme/_kovan_variables.scss
  20. 17
      apps/block_scout_web/assets/css/theme/_lukso_variables.scss
  21. 5
      apps/block_scout_web/assets/css/theme/_neutral_variables.scss
  22. 5
      apps/block_scout_web/assets/css/theme/_poa_variables.scss
  23. 5
      apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss
  24. 5
      apps/block_scout_web/assets/css/theme/_ropsten_variables.scss
  25. 5
      apps/block_scout_web/assets/css/theme/_rsk_variables.scss
  26. 5
      apps/block_scout_web/assets/css/theme/_sokol_variables.scss
  27. 3
      apps/block_scout_web/assets/js/app.js
  28. 7
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  29. 24
      apps/block_scout_web/assets/js/lib/history_chart.js
  30. 4
      apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js
  31. 29
      apps/block_scout_web/assets/js/lib/list_morph.js
  32. 10
      apps/block_scout_web/assets/js/lib/redux_helpers.js
  33. 28
      apps/block_scout_web/assets/js/lib/smart_contract/new_smart_contract_form.js
  34. 17
      apps/block_scout_web/assets/js/lib/utils.js
  35. 4
      apps/block_scout_web/assets/js/pages/address.js
  36. 4
      apps/block_scout_web/assets/js/pages/address/coin_balances.js
  37. 4
      apps/block_scout_web/assets/js/pages/address/internal_transactions.js
  38. 4
      apps/block_scout_web/assets/js/pages/address/logs.js
  39. 4
      apps/block_scout_web/assets/js/pages/address/transactions.js
  40. 4
      apps/block_scout_web/assets/js/pages/address/validations.js
  41. 19
      apps/block_scout_web/assets/js/pages/blocks.js
  42. 28
      apps/block_scout_web/assets/js/pages/chain.js
  43. 11
      apps/block_scout_web/assets/js/pages/dark-mode-switcher.js
  44. 4
      apps/block_scout_web/assets/js/pages/pending_transactions.js
  45. 4
      apps/block_scout_web/assets/js/pages/transaction.js
  46. 4
      apps/block_scout_web/assets/js/pages/transactions.js
  47. 144
      apps/block_scout_web/assets/js/pages/verification_form.js
  48. 4
      apps/block_scout_web/assets/webpack.config.js
  49. 5
      apps/block_scout_web/config/config.exs
  50. 11
      apps/block_scout_web/config/dev.exs
  51. 6
      apps/block_scout_web/config/prod.exs
  52. 14
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  53. 10
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  54. 16
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  55. 20
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
  56. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  57. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
  58. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  59. 18
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  60. 16
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  61. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  62. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  63. 20
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  64. 11
      apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
  65. 9
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
  66. 323
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex
  67. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  68. 21
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
  69. 11
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
  70. 21
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
  71. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex
  72. 4
      apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
  73. 14
      apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
  74. 6
      apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
  75. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
  76. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
  77. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
  78. 4
      apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
  79. 6
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
  80. 8
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
  81. 6
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
  82. 2
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
  83. 9
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
  84. 36
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  85. 1
      apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
  86. 4
      apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex
  87. 10
      apps/block_scout_web/lib/block_scout_web/router.ex
  88. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  89. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
  90. 4
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  91. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_coin_balance/index.html.eex
  92. 72
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  93. 16
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  94. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  95. 70
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  96. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  97. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  98. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  99. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  100. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.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.8.1-node-browsers - image: circleci/elixir:1.9.0-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -129,7 +129,7 @@ jobs:
check_formatted: check_formatted:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -143,7 +143,7 @@ jobs:
credo: credo:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -177,7 +177,7 @@ jobs:
dialyzer: dialyzer:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -191,9 +191,9 @@ jobs:
- restore_cache: - restore_cache:
keys: keys:
- v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }} - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }} - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }} - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- run: - run:
name: Unpack PLT cache name: Unpack PLT cache
@ -213,15 +213,15 @@ jobs:
cp ~/.mix/dialyxir*.plt plts/ cp ~/.mix/dialyxir*.plt plts/
- save_cache: - save_cache:
key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }} key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
paths: paths:
- plts - plts
- save_cache: - save_cache:
key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }} key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
paths: paths:
- plts - plts
- save_cache: - save_cache:
key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }} key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
paths: paths:
- plts - plts
@ -247,7 +247,7 @@ jobs:
gettext: gettext:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -286,7 +286,7 @@ jobs:
release: release:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: prod MIX_ENV: prod
@ -298,7 +298,7 @@ jobs:
- run: mix local.hex --force - run: mix local.hex --force
- run: mix local.rebar --force - run: mix local.rebar --force
- run: mix release --verbose --env prod - run: MIX_ENV=prod mix release
- run: - run:
name: Collecting artifacts name: Collecting artifacts
command: | command: |
@ -312,7 +312,7 @@ jobs:
sobelow: sobelow:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: test MIX_ENV: test
@ -336,7 +336,7 @@ jobs:
test_geth_http_websocket: test_geth_http_websocket:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1-node-browsers - image: circleci/elixir:1.9.0-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -390,7 +390,7 @@ jobs:
test_geth_mox: test_geth_mox:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1-node-browsers - image: circleci/elixir:1.9.0-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -444,7 +444,7 @@ jobs:
test_parity_http_websocket: test_parity_http_websocket:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1-node-browsers - image: circleci/elixir:1.9.0-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -498,7 +498,7 @@ jobs:
test_parity_mox: test_parity_mox:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1-node-browsers - image: circleci/elixir:1.9.0-node-browsers
environment: environment:
MIX_ENV: test MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below # match POSTGRES_PASSWORD for postgres image below
@ -552,7 +552,7 @@ jobs:
coveralls_merge: coveralls_merge:
docker: docker:
# Ensure .tool-versions matches # Ensure .tool-versions matches
- image: circleci/elixir:1.8.1 - image: circleci/elixir:1.9.0
environment: environment:
MIX_ENV: test MIX_ENV: test

@ -2,6 +2,12 @@
: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/block_scout_web/views/layout_view.ex:175
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: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() 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()
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'

@ -1,3 +1,3 @@
elixir 1.8.1 elixir 1.9
erlang 21.0.4 erlang 21.0.4
nodejs 10.11.0 nodejs 10.11.0

@ -2,17 +2,66 @@
### Features ### Features
### Fixes
### Chore
## 2.0.2-beta
### Features
- [#2412](https://github.com/poanetwork/blockscout/pull/2412) - dark theme
- [#2399](https://github.com/poanetwork/blockscout/pull/2399) - decode verified smart contract's logs
- [#2391](https://github.com/poanetwork/blockscout/pull/2391) - Controllers Improvements
- [#2379](https://github.com/poanetwork/blockscout/pull/2379) - Disable network selector when is empty
- [#2374](https://github.com/poanetwork/blockscout/pull/2374) - decode constructor arguments for verified smart contracts
- [#2366](https://github.com/poanetwork/blockscout/pull/2366) - paginate eth logs
- [#2360](https://github.com/poanetwork/blockscout/pull/2360) - add default evm version to smart contract verification
- [#2352](https://github.com/poanetwork/blockscout/pull/2352) - Fetch rewards in parallel with transactions
- [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint - [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint
- [#2324](https://github.com/poanetwork/blockscout/pull/2324) - set timeout for loading message on the main page
### Fixes ### Fixes
- [#2421](https://github.com/poanetwork/blockscout/pull/2421) - Fix hiding of loader for txs on the main page
- [#2420](https://github.com/poanetwork/blockscout/pull/2420) - fetch data from cache in healthy endpoint
- [#2416](https://github.com/poanetwork/blockscout/pull/2416) - Fix "page not found" handling in the router
- [#2413](https://github.com/poanetwork/blockscout/pull/2413) - remove outer tables for decoded data
- [#2410](https://github.com/poanetwork/blockscout/pull/2410) - preload smart contract for logs decoding
- [#2405](https://github.com/poanetwork/blockscout/pull/2405) - added templates for table loader and tile loader
- [#2398](https://github.com/poanetwork/blockscout/pull/2398) - show only one decoded candidate
- [#2389](https://github.com/poanetwork/blockscout/pull/2389) - Reduce Lodash lib size (86% of lib methods are not used)
- [#2388](https://github.com/poanetwork/blockscout/pull/2388) - add create2 support to geth's js tracer
- [#2387](https://github.com/poanetwork/blockscout/pull/2387) - fix not existing keys in transaction json rpc
- [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css
- [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info
- [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict
- [#2346](https://github.com/poanetwork/blockscout/pull/2346) - Avoid fetching internal transactions of blocks that still need refetching
- [#2350](https://github.com/poanetwork/blockscout/pull/2350) - fix invalid User agent headers
- [#2345](https://github.com/poanetwork/blockscout/pull/2345) - do not override existing market records
- [#2337](https://github.com/poanetwork/blockscout/pull/2337) - set url params for prod explicitly
- [#2341](https://github.com/poanetwork/blockscout/pull/2341) - fix transaction input json encoding
- [#2311](https://github.com/poanetwork/blockscout/pull/2311) - fix market history overriding with zeroes
- [#2310](https://github.com/poanetwork/blockscout/pull/2310) - parse url for api docs
- [#2299](https://github.com/poanetwork/blockscout/pull/2299) - fix interpolation in error message - [#2299](https://github.com/poanetwork/blockscout/pull/2299) - fix interpolation in error message
- [#2303](https://github.com/poanetwork/blockscout/pull/2303) - fix transaction csv download link - [#2303](https://github.com/poanetwork/blockscout/pull/2303) - fix transaction csv download link
- [#2304](https://github.com/poanetwork/blockscout/pull/2304) - footer grid fix for md resolution - [#2304](https://github.com/poanetwork/blockscout/pull/2304) - footer grid fix for md resolution
- [#2291](https://github.com/poanetwork/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue - [#2291](https://github.com/poanetwork/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue
- [#2326](https://github.com/poanetwork/blockscout/pull/2326) - fix nested constructor arguments
### Chore ### Chore
- [#2422](https://github.com/poanetwork/blockscout/pull/2422) - check if address_id is binary in token_transfers_csv endpoint
- [#2418](https://github.com/poanetwork/blockscout/pull/2418) - Remove parentheses in market cap percentage
- [#2401](https://github.com/poanetwork/blockscout/pull/2401) - add ENV vars to manage updating period of average block time and market history cache
- [#2363](https://github.com/poanetwork/blockscout/pull/2363) - add parameters example for eth rpc
- [#2342](https://github.com/poanetwork/blockscout/pull/2342) - Upgrade Postgres image version in Docker setup
- [#2325](https://github.com/poanetwork/blockscout/pull/2325) - Reduce function input to address' hash only where possible
- [#2323](https://github.com/poanetwork/blockscout/pull/2323) - Group Explorer caches
- [#2305](https://github.com/poanetwork/blockscout/pull/2305) - Improve Address controllers
- [#2302](https://github.com/poanetwork/blockscout/pull/2302) - fix names for xDai source - [#2302](https://github.com/poanetwork/blockscout/pull/2302) - fix names for xDai source
- [#2289](https://github.com/poanetwork/blockscout/pull/2289) - Optional websockets for dev environment - [#2289](https://github.com/poanetwork/blockscout/pull/2289) - Optional websockets for dev environment
- [#2307](https://github.com/poanetwork/blockscout/pull/2307) - add GoJoy to README
- [#2293](https://github.com/poanetwork/blockscout/pull/2293) - remove request idle timeout configuration
- [#2255](https://github.com/poanetwork/blockscout/pull/2255) - bump elixir version to 1.9.0
## 2.0.1-beta ## 2.0.1-beta
@ -99,6 +148,7 @@
### Chore ### Chore
- [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version
- [#2118](https://github.com/poanetwork/blockscout/pull/2118) - show only the last decompiled contract - [#2118](https://github.com/poanetwork/blockscout/pull/2118) - show only the last decompiled contract
- [#2255](https://github.com/poanetwork/blockscout/pull/2255) - upgrade elixir version to 1.9.0
- [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver - [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver
## 2.0.0-beta ## 2.0.0-beta

@ -41,6 +41,7 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | | | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) | | | | [Loom](http://plasma-blockexplorer.dappchains.com/) |
| | | [Tenda](https://tenda.network) | | | | [Tenda](https://tenda.network) |
| | | [GoJoy Chain](https://gojoychain.com/) |
Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938). Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).

@ -1,4 +1,3 @@
import _ from 'lodash'
import { reducer, initialState } from '../../js/pages/pending_transactions' import { reducer, initialState } from '../../js/pages/pending_transactions'
test('CHANNEL_DISCONNECTED', () => { test('CHANNEL_DISCONNECTED', () => {

@ -128,6 +128,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@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";
@import "theme/dark-theme";
:export { :export {
dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color; dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color;
@ -136,4 +137,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
dashboardLineColorTransactions: $dashboard-line-color-transactions; dashboardLineColorTransactions: $dashboard-line-color-transactions;
primary: $primary; primary: $primary;
secondary: $secondary; secondary: $secondary;
darkprimary: $dark-primary;
darksecondary: $dark-secondary;
} }

@ -61,10 +61,6 @@ $footer-logo-width: auto !default;
} }
} }
.footer-info {
padding-top: 1em;
}
.footer-link { .footer-link {
color: $footer-link-color; color: $footer-link-color;

@ -253,3 +253,9 @@ $navbar-logo-width: auto !default;
padding-right: 0; padding-right: 0;
} }
} }
.nav-item-networks {
.topnav-nav-link {
transition: none !important;
}
}

@ -287,16 +287,6 @@ $network-selector-item-icon-dimensions: 30px !default;
} }
} }
.network-selector-load-more-container {
flex-shrink: 1;
padding: 0 $network-selector-horizontal-padding;
.btn-network-selector-load-more {
@include btn-line($btn-network-selector-load-more-background, $btn-network-selector-load-more-color);
width: 100%;
}
}
.network-selector-item-favorite { .network-selector-item-favorite {
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;

@ -26,6 +26,43 @@ $stakes-table-cell-separation: 25px !default;
} }
} }
// Loader
.table-content-loader {
display: inline-block;
height: 24px;
width: 100%;
border-radius: 4px;
background-color: #f5f6fa;
overflow: hidden;
position: relative;
&:before {
width: inherit;
height: inherit;
content: '';
position: absolute;
background: linear-gradient(to right, #f5f6fa 2%, #eee 18%, #f5f6fa 33%);
animation-duration: 1.7s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-name: placeholderAnimate;
background-size: 1300px;
}
}
@keyframes placeholderAnimate {
0%{ background-position: -650px 0; }
100% { background-position: 650px 0; }
}
.table-content-pseudo {
td {
&:last-child {
padding-right: 24px !important;
}
}
}
.stakes-table { .stakes-table {
min-width: fit-content; min-width: fit-content;
width: 100%; width: 100%;

@ -339,9 +339,9 @@ $tile-body-a-color: #5959d8 !default;
padding-left: 6px; padding-left: 6px;
padding-right: 6px; padding-right: 6px;
} }
.tile-type-block { }
overflow: hidden; .tile-type-block {
} overflow: hidden;
} }
.row { .row {
@include media-breakpoint-down(lg) { @include media-breakpoint-down(lg) {
@ -351,3 +351,214 @@ $tile-body-a-color: #5959d8 !default;
} }
} }
} }
// Loader
.tile-type-loading {
background-color: #fff;
padding-top: 30px;
padding-bottom: 28px;
}
.tile-loader {
display: inline-block;
height: 20px;
width: 100%;
border-radius: 4px;
background-color: #f5f6fa;
overflow: hidden;
position: relative;
&:before {
width: inherit;
height: inherit;
content: '';
position: absolute;
background: linear-gradient(to right, #f5f6fa 2%, #eee 18%, #f5f6fa 33%);
animation-duration: 1.7s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-name: tilePlaceholderAnimate;
background-size: 1300px;
}
}
.tile-label-loader {
height: 14px;
width: 80px;
}
.tile-address-loader {
& + .tile-address-loader {
margin-top: 6px;
}
}
@keyframes tilePlaceholderAnimate {
0%{ background-position: -650px 0; }
100% { background-position: 650px 0; }
}
// Loading Animation
@keyframes playBlockLoadingAnimation {
0%, 90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
[data-selector="chain-block-list"] {
.col-lg-3:first-child {
.tile-type-block-animation {
animation: playBlockLoadingAnimation 2.1s linear forwards;
}
}
}
.fade-up-blocks-chain {
.tile-type-block {
position: relative;
}
.tile-type-block-animation {
opacity: 0;
position: absolute;
top: -1px;
left: -4px;
width: calc(100% + 5px);
height: calc(100% + 2px);
background-color: #F6F7F9;
border-radius: 4px;
overflow: hidden;
transition: .24s ease-out;
border-top: 1px solid #dee2e6;
border-right: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
pointer-events: none;
.tile-type-line-up {
position: absolute;
bottom: -1px;
left: 0;
height: calc(100% + 2px);
width: 4px;
background-color: $tile-type-block-color;
transform: scaleY(0);
transform-origin: center bottom;
animation: blockLoaderLine 2s linear forwards;
z-index: 2;
}
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 1px;
background-color: #dee2e6;
}
}
}
.cube-animation-title {
font-size: 12px;
color: #a3a9b5;
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
.fade-up-blocks-chain:first-child {
.tile-type-block-animation {
opacity: 1;
}
}
@keyframes blockLoaderLine {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
$cube-bezier: cubic-bezier(.25,.8,.25,1);
$cube-quantity: 5;
.cube-animation-wrapper {
width: 560px;
height: 290px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.26);
svg {
width: 50px;
margin-top: -29px;
.side {
fill: $tile-type-block-color;
opacity: 1;
&:nth-of-type(2) {
fill: lighten($tile-type-block-color, 30);
opacity: 0.5;
}
&:nth-of-type(3) {
fill: lighten($tile-type-block-color, 80);
opacity: 0.5;
}
}
}
@while $cube-quantity > 0 {
.cube-animation-row:nth-of-type(#{$cube-quantity}) {
left: 25px * $cube-quantity;
top: 15px * $cube-quantity;
}
.cube-animation-column:nth-of-type(#{$cube-quantity}) {
position: relative;
top: 14px * $cube-quantity;
left: 25px * $cube-quantity;
}
.cube-animation-column:nth-of-type(#{$cube-quantity}) svg {
transform: translate3d(0,0,0);
animation: shrink-expand 2.8s $cube-bezier forwards;
animation-delay: -0.16s * $cube-quantity;
}
$cube-quantity: $cube-quantity - 1;
}
}
.cube-animation-center {
position: absolute;
top: 6%;
left: 20%;
}
.cube-animation-row {
display: flex;
flex-direction: row-reverse;
position: absolute;
}
.cube-animation-column {
display: flex;
flex-direction: column-reverse;
}
@keyframes shrink-expand {
0% {
transform: scale(0);
}
50% {
transform: scale(1);
}
100% {
transform: scale(0);
}
}

@ -70,7 +70,10 @@ $colors: map-merge(
); );
$primary: $indigo !default; $primary: $indigo !default;
$dark-primary: #9b62ff !default;
$dark-primary-alternate: #9b62ff !default;
$secondary: #7dd79f !default; $secondary: #7dd79f !default;
$dark-secondary: #87e1a9 !default;
$tertiary: $purple !default; $tertiary: $purple !default;
$success: $green !default; $success: $green !default;
$info: $cyan !default; $info: $cyan !default;

@ -63,3 +63,8 @@ $card-tab-active: $secondary;
$badge-neutral-color: #20446e; $badge-neutral-color: #20446e;
$badge-neutral-background-color: rgba(#20446e, .1); $badge-neutral-background-color: rgba(#20446e, .1);
$api-text-monospace-color: #20446e; $api-text-monospace-color: #20446e;
// Dark theme
$dark-primary: #15bba6;
$dark-secondary: #93d7ff;
$dark-primary-alternate: #15bba6;

@ -0,0 +1,470 @@
$body-dark: #1c1d31; // body background
$dark-bg: #22223a; // hero shade
$dark-light-bg: #282945; // pills bg shade
$dark-light: #313355; // tile light top share
$labels-dark: #8a8dba; // header nav, labels
// Switcher
.dark-mode-changer {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: transparent;
border: none;
cursor: pointer;
margin-right: 5px;
outline: none !important;
box-shadow: none !important;
transition: .2s ease-in;
&:hover {
opacity: .8;
}
svg path {
fill: #828ba0;
}
&--dark {
svg path {
fill: $dark-primary;
}
}
}
.dark-theme-applied {
color: #fff;
// navbar
.navbar.navbar-primary {
background-color: $dark-light-bg;
}
.navbar-brand .navbar-logo {
filter: brightness(0) invert(1);
}
.navbar.navbar-primary .navbar-nav .nav-link {
color: $labels-dark;
.nav-link-icon {
svg path {
fill: $labels-dark;
}
}
&.active, &:hover {
.nav-link-icon {
svg path {
fill: $dark-primary;
}
}
&:before {
background-color: $dark-primary;
}
}
}
.navbar.navbar-primary .form-control {
background-color: $dark-bg;
border-color: $dark-bg;
color: #fff;
&::placeholder {
color: $labels-dark;
}
}
.navbar.navbar-primary .navbar-toggler .navbar-toggler-icon {
filter: invert(1);
}
// footer
.footer {
background: $dark-light-bg;
color: $labels-dark;
}
.footer-social-icon,
.footer-link {
color: $labels-dark;
}
.footer-social-icon:hover,
.footer-link:hover {
color: #fff;
}
.footer-list ul li::before {
background-color: $dark-secondary;
}
// hero stats
.layout-container .dashboard-banner-container {
background-image: none;
background-color: $dark-bg;
}
.dashboard-banner-network-plain-container,
.dashboard-banner-network-plain-container::after {
background-color: $dark-light-bg;
}
.dashboard-banner-network-stats-label,
.dashboard-banner-chart-legend .dashboard-banner-chart-legend-label {
color: $labels-dark;
}
.dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before {
background-color: $dark-primary;
}
.dashboard-banner-network-stats-item::before {
background-color: $dark-secondary;
}
.dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before {
background-color: $dark-secondary;
}
// main container, layout, cards
.layout-container main {
background-color: $body-dark;
}
.card {
background-color: $dark-light-bg;
box-shadow: 0 0 30px 0 rgba(23, 24, 41, 0.5);
}
.card-header {
border-bottom-color: darken($labels-dark, 30);
}
.address-detail-hash-title {
color: #fff;
}
.card-tabs {
border-bottom-color: darken($labels-dark, 30);
}
.card-tab {
background-color: transparent;
&:hover:not(.active) {
background-color: rgba($dark-primary, .15);
color: $dark-primary;
}
&.active {
background-color: $dark-primary-alternate;
color: #fff;
}
}
.card-background-1 {
background-color: $dark-primary-alternate;
}
// Components
a {
color: $dark-primary;
}
.tile {
border-top-color: $dark-light;
border-bottom-color: $dark-light;
border-right-color: $dark-light;
background-color: $dark-light;
color: $labels-dark;
&:not([class^="tile tile-type"]) {
border-left-color: $dark-light;
}
&.tile-type-coin-balance {
border-left-color: $dark-light;
}
.tile-title {
color: #fff;
}
.tile-transaction-type-block {
background-color: transparent;
}
}
.tile-bottom-contents {
background-color: $dark-bg;
}
a.tile-title {
color: #fff !important;
}
.tile.tile-type-block .tile-transaction-type-block a {
color: #fff;
}
.fade-up-blocks-chain .tile-type-block-animation {
background-color: $dark-light;
border-top-color: $dark-light;
border-right-color: $dark-light;
border-bottom-color: $dark-light;
}
.fade-up-blocks-chain .tile-type-block-animation:after {
background-color: $dark-light;
}
.cube-animation-title {
color: $labels-dark;
}
.tile .tile-body a,
.tile span[data-address-hash] { color: $dark-primary; }
.fade-up-blocks-chain .tile-type-block-animation .tile-type-line-up {
background-color: $dark-primary;
}
.tile.tile-type-block {
border-left-color: $dark-primary
}
.tile.tile-type-block .tile-status-label {
color: $dark-primary;
}
.tile.tile-type-block .tile-transaction-type-block {
border-right-color: $dark-primary;
border-top-color: $dark-primary;
border-bottom-color: $dark-primary;
}
.tile .progress {
background-color: rgba(#fff, .2);
}
.tile .progress .progress-bar {
background-color: $dark-primary;
}
.tile .tile-title-lg:not([data-balance-change-sign]) {
color: $dark-primary;
}
// btns
.btn-line {
background-color: transparent;
border-color: $dark-primary;
color: $dark-primary;
&:hover {
border-color: $dark-primary;
background-color: $dark-primary;
color: #fff;
}
}
.btn-copy-icon, .btn-qr-icon {
border-color: $dark-primary;
path {
fill: $dark-primary;
}
&:hover {
background-color: $dark-primary;
path {
fill: #fff;
}
}
}
// pagination
.pagination-container .pagination .page-link {
color: $labels-dark;
border-color: $dark-light;
background-color: $dark-light;
&:not(.no-hover):hover {
color: #fff;
background-color: $dark-primary;
path {
fill: #fff;
}
}
&[disabled] {
color: $labels-dark;
border-color: $dark-light;
background-color: $dark-light;
}
}
// dropdown
.dropdown-menu {
background-color: $dark-light;
border-left-color: $dark-light;
border-right-color: $dark-light;
border-bottom-color: $dark-light;
}
.dropdown-item {
color: #fff;
&:hover {
background-color: rgba(#fff, .1);
}
}
.dropdown-item.active {
background-color: $dark-primary;
}
.btn-dropdown-line {
background-color: $dark-light;
border-color: $dark-light;
color: $labels-dark;
}
// table
.stakes-table-th {
background-color: $dark-light;
color: $labels-dark;
}
.stakes-td {
border-bottom-color: darken($labels-dark, 30);
}
.table th, .table td {
border-top-color: darken($labels-dark, 30);
}
hr {
border-top-color: darken($labels-dark, 30);
}
// api's
.api-anchors-list {
background-color: $dark-light;
}
.api-doc-list-item {
border-bottom-color: darken($labels-dark, 30);
}
.card-subtitle,
.api-anchors-list-item-title,
.api-doc-list-item-title {
color: #fff;
}
.api-text-monospace {
color: $dark-primary;
}
.api-text-monospace-background {
background-color: rgba($dark-primary, .15);
}
.badge.badge-neutral {
background-color: rgba($dark-primary, .15);
color: $dark-primary;
}
// download csv button
.download-all-transactions .download-all-transactions-link svg path {
fill: $dark-primary;
}
//tooltips
.tooltip .arrow:before {
border-top-color: $dark-primary;
border-bottom-color: $dark-primary;
}
.tooltip > .tooltip-inner {background-color: $dark-primary;}
//network select
.network-selector-overlay {
background-color: rgba($dark-bg, .9);
}
.network-selector {
background-color: $dark-light-bg;
}
.network-selector-title {
color: #fff;
}
.network-selector-text {
color: $labels-dark;
}
.network-selector-close path {
fill: #fff;
}
.network-selector-search-container {
background-color: $dark-light;
}
.network-selector-search-container path {
fill: $labels-dark;
}
.network-selector-search-input {
color: #fff !important;
&::placeholder {
color: $labels-dark;
}
}
.network-selector-tab {
color: $labels-dark;
&:hover, &.active {
color: #fff;
}
&.active {
&:after {
background-color: $dark-primary;
}
}
}
.network-selector-item,
.network-selector-tabs-container {
border-bottom-color: darken($labels-dark, 30);
}
.network-selector-item-title {
color: #fff;
}
.network-selector-item-type {
color: $labels-dark;
}
.radio .radio-icon {
border-color: $labels-dark
}
.network-selector-item-url:hover .network-selector-item-type {
color: #fff;
}
//coin dropdown
.token-balance-dropdown.dropdown-menu {
border-color: $dark-light !important;
box-shadow: 0 0 30px 0 rgba(23, 24, 41, 0.5) !important;
}
.token-balance-dropdown .dropdown-search-icon path {
fill: $labels-dark;
}
.token-balance-dropdown .dropdown-search-field {
background-color: $dark-light;
border-color: $dark-light;
color: #fff;
&::placeholder {
color: $labels-dark;
}
}
.token-balance-dropdown[aria-labelledby="dropdown-tokens"] .dropdown-items .dropdown-item:hover {
color: #fff !important;
}
.dropdown-header {
color: $labels-dark;
}
.border-bottom {
border-bottom-color: darken($labels-dark, 30) !important;
}
// coin balance history chart
.chartjs-render-monitor[data-chart="coinBalanceHistoryChart"] {
filter: brightness(0) invert(1) !important;
}
// logs search
.logs-search-input, .logs-search-btn, .logs-search-btn-cancel {
background-color: $dark-light;
border-color: $dark-light;
color: #fff;
}
.logs-search-btn {
color: $labels-dark;
}
.logs-search-btn {
&:hover {
background-color: $dark-primary;
color: #fff;
}
}
.logs-search-input {
&::placeholder {
color: $labels-dark;
}
}
// code
pre {
color: #fff;
}
// info allert
.alert-info {
color: $labels-dark;
background-color: $dark-light;
border-color: $dark-light;
}
// dark text
.text-dark {
color: #fff;
}
}

@ -75,3 +75,8 @@ $card-tab-active: $tertiary;
$badge-neutral-color: $tertiary; $badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1); $badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary; $api-text-monospace-color: $tertiary;
// Dark theme
$dark-primary: #8588ff;
$dark-secondary: #4ad7a7;
$dark-primary-alternate: #5b5ed8;

@ -57,3 +57,17 @@ $card-tab-active: $secondary;
); );
} }
} }
// Dark theme
$dark-primary: #49a2ee;
$dark-secondary: #4ad7a7;
$dark-primary-alternate: #49a2ee;
.dark-theme-applied {
.dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before {
background-color: $dark-primary !important;
}
.dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before {
background-color: $dark-secondary !important;
}
}

@ -78,3 +78,8 @@ $card-tab-active: $sub-accent-color;
$badge-neutral-color: $sub-accent-color; $badge-neutral-color: $sub-accent-color;
$badge-neutral-background-color: rgba($sub-accent-color, .1); $badge-neutral-background-color: rgba($sub-accent-color, .1);
$api-text-monospace-color: $sub-accent-color; $api-text-monospace-color: $sub-accent-color;
// Dark theme
$dark-primary: #e1995a;
$dark-secondary: #aeaeae;
$dark-primary-alternate: #e1995a;

@ -75,3 +75,8 @@ $badge-success-background-color: rgba(#15bba6, .1);
$badge-neutral-color: $tertiary; $badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1); $badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary; $api-text-monospace-color: $tertiary;
// Dark theme
$dark-primary: #42e2d7;
$dark-secondary: #1f857f;
$dark-primary-alternate: #1f857f;

@ -151,3 +151,20 @@ $dashboard-banner-network-plain-container-height: 150px;
$badge-neutral-color: $tertiary; $badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1); $badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary; $api-text-monospace-color: $tertiary;
// Dark theme
$dark-primary: #fdcec4;
$dark-secondary: #a96c55;
$dark-primary-alternate: #a96c55;
.dark-theme-applied {
.dashboard-banner-network-stats-value {
color: $dark-primary !important;
}
.layout-container .dashboard-banner-container {
background-color: #282945 !important;
}
.dashboard-banner-network-plain-container::after {
box-shadow: none !important;
}
}

@ -72,3 +72,8 @@ $api-text-monospace-color: $primary;
color: $primary !important; color: $primary !important;
} }
} }
// Dark theme
$dark-primary: #9b62ff;
$dark-secondary: #87e1a9;
$dark-primary-alternate: #7e50d0;

@ -72,3 +72,8 @@ $api-text-monospace-color: $primary;
color: $primary !important; color: $primary !important;
} }
} }
// Dark theme
$dark-primary: #9b62ff;
$dark-secondary: #87e1a9;
$dark-primary-alternate: #7e50d0;

@ -57,3 +57,8 @@ $card-tab-active: $secondary;
); );
} }
} }
// Dark theme
$dark-primary: #38a9f5;
$dark-secondary: #76f1ff;
$dark-primary-alternate: #38a9f5;

@ -57,3 +57,8 @@ $card-tab-active: $secondary;
); );
} }
} }
// Dark theme
$dark-primary: #38a9f5;
$dark-secondary: #76f1ff;
$dark-primary-alternate: #38a9f5;

@ -65,3 +65,8 @@ $card-tab-active: $secondary;
// Badges // Badges
$badge-neutral-color: #1a323b; $badge-neutral-color: #1a323b;
$badge-neutral-background-color: rgba(#1a323b, .1); $badge-neutral-background-color: rgba(#1a323b, .1);
// Dark theme
$dark-primary: #38c5a4;
$dark-secondary: #e39a54;
$dark-primary-alternate: #30ab8d;

@ -73,3 +73,8 @@ $card-tab-active: $sub-accent-color;
// Badges // Badges
$badge-neutral-color: $tertiary; $badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1); $badge-neutral-background-color: rgba($tertiary, .1);
// Dark theme
$dark-primary: #40bfb2;
$dark-secondary: #25c9ff;
$dark-primary-alternate: #1c9f90;

@ -34,6 +34,8 @@ import './pages/transactions'
import './pages/favorites' import './pages/favorites'
import './pages/network-search' import './pages/network-search'
import './pages/layout' import './pages/layout'
import './pages/verification_form'
import './pages/dark-mode-switcher'
import './pages/admin/tasks.js' import './pages/admin/tasks.js'
@ -46,7 +48,6 @@ import './lib/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/new_smart_contract_form'
import './lib/smart_contract/read_only_functions' import './lib/smart_contract/read_only_functions'
import './lib/smart_contract/wei_ether_converter' import './lib/smart_contract/wei_ether_converter'
import './lib/stop_propagation' import './lib/stop_propagation'

@ -1,5 +1,6 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import map from 'lodash/map'
import merge from 'lodash/merge'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import listMorph from '../lib/list_morph' import listMorph from '../lib/list_morph'
@ -164,7 +165,7 @@ export const elements = {
if (state.itemKey) { if (state.itemKey) {
const container = $el[0] const container = $el[0]
const newElements = _.map(state.items, (item) => $(item)[0]) const newElements = map(state.items, (item) => $(item)[0])
listMorph(container, newElements, { key: state.itemKey }) listMorph(container, newElements, { key: state.itemKey })
return return
} }
@ -244,7 +245,7 @@ export const elements = {
* adding or removing with the correct animation. Check list_morph.js for more informantion. * adding or removing with the correct animation. Check list_morph.js for more informantion.
*/ */
export function createAsyncLoadStore (reducer, initialState, itemKey) { export function createAsyncLoadStore (reducer, initialState, itemKey) {
const state = _.merge(asyncInitialState, initialState) const state = merge(asyncInitialState, initialState)
const store = createStore(reduceReducers(asyncReducer, reducer, state)) const store = createStore(reduceReducers(asyncReducer, reducer, state))
if (typeof itemKey !== 'undefined') { if (typeof itemKey !== 'undefined') {

@ -4,6 +4,7 @@ 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',
@ -103,6 +104,17 @@ function getMarketCapData (marketHistoryData, availableSupply) {
} }
} }
// colors for light and dark theme
var priceLineColor
var mcapLineColor
if (localStorage.getItem('current-color-mode') === 'dark') {
priceLineColor = sassVariables.darkprimary
mcapLineColor = sassVariables.darksecondary
} else {
priceLineColor = sassVariables.dashboardLineColorPrice
mcapLineColor = sassVariables.dashboardLineColorMarket
}
class MarketHistoryChart { class MarketHistoryChart {
constructor (el, availableSupply, marketHistoryData, dataConfig) { constructor (el, availableSupply, marketHistoryData, dataConfig) {
@ -118,8 +130,8 @@ class MarketHistoryChart {
data: [], data: [],
fill: false, fill: false,
pointRadius: 0, pointRadius: 0,
backgroundColor: sassVariables.dashboardLineColorPrice, backgroundColor: priceLineColor,
borderColor: sassVariables.dashboardLineColorPrice, borderColor: priceLineColor,
lineTension: 0 lineTension: 0
} }
if (dataConfig.market == undefined || dataConfig.market.indexOf("price") == -1){ if (dataConfig.market == undefined || dataConfig.market.indexOf("price") == -1){
@ -133,8 +145,8 @@ class MarketHistoryChart {
data: [], data: [],
fill: false, fill: false,
pointRadius: 0, pointRadius: 0,
backgroundColor: sassVariables.dashboardLineColorMarket, backgroundColor: mcapLineColor,
borderColor: sassVariables.dashboardLineColorMarket, borderColor: mcapLineColor,
lineTension: 0 lineTension: 0
} }
if (dataConfig.market == undefined || dataConfig.market.indexOf("market_cap") == -1){ if (dataConfig.market == undefined || dataConfig.market.indexOf("market_cap") == -1){
@ -186,6 +198,10 @@ export function createMarketHistoryChart (el) {
const dataConfig = $(el).data('history_chart_config') const dataConfig = $(el).data('history_chart_config')
const $chartLoading = $('[data-chart-loading-message]') 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, [], dataConfig) const chart = new MarketHistoryChart(el, 0, [], dataConfig)
Object.keys(dataPaths).forEach(function(history_source){ Object.keys(dataPaths).forEach(function(history_source){

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import { connectElements } from './redux_helpers.js' import { connectElements } from './redux_helpers.js'
@ -12,7 +12,7 @@ const initialState = {
function infiniteScrollReducer (state = initialState, action) { function infiniteScrollReducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'INFINITE_SCROLL_ELEMENTS_LOAD': { case 'INFINITE_SCROLL_ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'LOADING_NEXT_PAGE': { case 'LOADING_NEXT_PAGE': {
return Object.assign({}, state, { return Object.assign({}, state, {

@ -1,5 +1,10 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import map from 'lodash/map'
import get from 'lodash/get'
import noop from 'lodash/noop'
import find from 'lodash/find'
import intersectionBy from 'lodash/intersectionBy'
import differenceBy from 'lodash/differenceBy'
import morph from 'nanomorph' import morph from 'nanomorph'
import { updateAllAges } from './from_now' import { updateAllAges } from './from_now'
@ -25,12 +30,12 @@ import { updateAllAges } from './from_now'
export default function (container, newElements, { key, horizontal } = {}) { export default function (container, newElements, { key, horizontal } = {}) {
if (!container) return if (!container) return
const oldElements = $(container).children().get() const oldElements = $(container).children().get()
let currentList = _.map(oldElements, (el) => ({ id: _.get(el, key), el })) let currentList = map(oldElements, (el) => ({ id: get(el, key), el }))
const newList = _.map(newElements, (el) => ({ id: _.get(el, key), el })) const newList = map(newElements, (el) => ({ id: get(el, key), el }))
const overlap = _.intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] })) const overlap = intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] }))
// remove old items // remove old items
const removals = _.differenceBy(currentList, newList, 'id') const removals = differenceBy(currentList, newList, 'id')
let canAnimate = !horizontal && removals.length <= 1 let canAnimate = !horizontal && removals.length <= 1
removals.forEach(({ el }) => { removals.forEach(({ el }) => {
if (!canAnimate) return el.remove() if (!canAnimate) return el.remove()
@ -38,7 +43,7 @@ export default function (container, newElements, { key, horizontal } = {}) {
$el.addClass('shrink-out') $el.addClass('shrink-out')
setTimeout(() => { slideUpRemove($el) }, 400) setTimeout(() => { slideUpRemove($el) }, 400)
}) })
currentList = _.differenceBy(currentList, removals, 'id') currentList = differenceBy(currentList, removals, 'id')
// update kept items // update kept items
currentList = currentList.map(({ el }, i) => ({ currentList = currentList.map(({ el }, i) => ({
@ -47,14 +52,14 @@ export default function (container, newElements, { key, horizontal } = {}) {
})) }))
// add new items // add new items
const finalList = newList.map(({ id, el }) => _.get(_.find(currentList, { id }), 'el', el)).reverse() const finalList = newList.map(({ id, el }) => get(find(currentList, { id }), 'el', el)).reverse()
canAnimate = !horizontal canAnimate = !horizontal
finalList.forEach((el, i) => { finalList.forEach((el, i) => {
if (el.parentElement) return if (el.parentElement) return
if (!canAnimate) return container.insertBefore(el, _.get(finalList, `[${i - 1}]`)) if (!canAnimate) return container.insertBefore(el, get(finalList, `[${i - 1}]`))
canAnimate = false canAnimate = false
if (!_.get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el) if (!get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el)
slideDownBefore($(_.get(finalList, `[${i - 1}]`)), el) slideDownBefore($(get(finalList, `[${i - 1}]`)), el)
}) })
} }
@ -80,7 +85,7 @@ function slideUpRemove ($el) {
}) })
} }
function smarterSlideDown ($el, { insert = _.noop } = {}) { function smarterSlideDown ($el, { insert = noop } = {}) {
if (!$el.length) return if (!$el.length) return
const originalScrollHeight = document.body.scrollHeight const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY const scrollPosition = window.scrollY
@ -100,7 +105,7 @@ function smarterSlideDown ($el, { insert = _.noop } = {}) {
} }
} }
function smarterSlideUp ($el, { complete = _.noop } = {}) { function smarterSlideUp ($el, { complete = noop } = {}) {
if (!$el.length) return if (!$el.length) return
const originalScrollHeight = document.body.scrollHeight const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY const scrollPosition = window.scrollY

@ -1,5 +1,7 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import reduce from 'lodash/reduce'
import isObject from 'lodash/isObject'
import forIn from 'lodash/forIn'
import { createStore as reduxCreateStore } from 'redux' import { createStore as reduxCreateStore } from 'redux'
/** /**
@ -97,17 +99,17 @@ export function createStore (reducer) {
*/ */
export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) { export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) {
function loadElements () { function loadElements () {
return _.reduce(elements, (pageLoadParams, { load }, selector) => { return reduce(elements, (pageLoadParams, { load }, selector) => {
if (!load) return pageLoadParams if (!load) return pageLoadParams
const $el = $(selector) const $el = $(selector)
if (!$el.length) return pageLoadParams if (!$el.length) return pageLoadParams
const morePageLoadParams = load($el, store) const morePageLoadParams = load($el, store)
return _.isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams
}, {}) }, {})
} }
function renderElements (state, oldState) { function renderElements (state, oldState) {
_.forIn(elements, ({ render }, selector) => { forIn(elements, ({ render }, selector) => {
if (!render) return if (!render) return
const $el = $(selector) const $el = $(selector)
if (!$el.length) return if (!$el.length) return

@ -1,28 +0,0 @@
import $ from 'jquery'
$(function () {
$('.js-btn-add-contract-libraries').on('click', function () {
$('.js-smart-contract-libraries-wrapper').show()
$(this).hide()
})
$('.js-smart-contract-form-reset').on('click', function () {
$('.js-contract-library-form-group').removeClass('active')
$('.js-contract-library-form-group').first().addClass('active')
$('.js-smart-contract-libraries-wrapper').hide()
$('.js-btn-add-contract-libraries').show()
$('.js-add-contract-library-wrapper').show()
})
$('.js-btn-add-contract-library').on('click', function () {
let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
if (nextContractLibrary) {
nextContractLibrary.addClass('active')
}
if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) {
$('.js-add-contract-library-wrapper').hide()
}
})
})

@ -1,8 +1,8 @@
import _ from 'lodash' import debounce from 'lodash/debounce'
export function batchChannel (func) { export function batchChannel (func) {
let msgs = [] let msgs = []
const debouncedFunc = _.debounce(() => { const debouncedFunc = debounce(() => {
func.apply(this, [msgs]) func.apply(this, [msgs])
msgs = [] msgs = []
}, 1000, { maxWait: 5000 }) }, 1000, { maxWait: 5000 })
@ -11,3 +11,16 @@ export function batchChannel (func) {
debouncedFunc() debouncedFunc()
} }
} }
export function showLoader (isTimeout, loader) {
if (isTimeout) {
const timeout = setTimeout(function () {
loader.removeAttr('hidden')
loader.show()
}, 1000)
return timeout
} else {
loader.hide()
return null
}
}

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
@ -25,7 +25,7 @@ export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' 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'
@ -14,7 +14,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../../socket' import socket from '../../socket'
@ -20,7 +20,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' 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'
@ -13,7 +13,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
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})

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import URI from 'urijs' import URI from 'urijs'
import humps from 'humps' import humps from 'humps'
import { subscribeChannel } from '../../socket' import { subscribeChannel } from '../../socket'
@ -16,7 +16,7 @@ export function reducer (state, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state if (state.beyondPageOne) return state

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' 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'
@ -14,7 +14,7 @@ export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'PAGE_LOAD': case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { channelDisconnected: true }) return Object.assign({}, state, { channelDisconnected: true })

@ -1,5 +1,10 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import last from 'lodash/last'
import min from 'lodash/min'
import max from 'lodash/max'
import keys from 'lodash/keys'
import rangeRight from 'lodash/rangeRight'
import humps from 'humps' 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'
@ -14,7 +19,7 @@ export const blockReducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) { function baseReducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { return Object.assign({}, state, {
@ -25,7 +30,7 @@ function baseReducer (state = initialState, action) {
if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state
const blockNumber = getBlockNumber(action.msg.blockHtml) const blockNumber = getBlockNumber(action.msg.blockHtml)
const minBlock = getBlockNumber(_.last(state.items)) const minBlock = getBlockNumber(last(state.items))
if (state.items.length && blockNumber < minBlock) return state if (state.items.length && blockNumber < minBlock) return state
@ -62,12 +67,12 @@ function withMissingBlocks (reducer) {
return acc return acc
}, {}) }, {})
const blockNumbers = _(blockNumbersToItems).keys().map(x => parseInt(x, 10)).value() const blockNumbers = keys(blockNumbersToItems).map(x => parseInt(x, 10))
const minBlock = _.min(blockNumbers) const minBlock = min(blockNumbers)
const maxBlock = _.max(blockNumbers) const maxBlock = max(blockNumbers)
return Object.assign({}, result, { return Object.assign({}, result, {
items: _.rangeRight(minBlock, maxBlock + 1) items: rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber)) .map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber))
}) })
} }

@ -1,11 +1,15 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import first from 'lodash/first'
import rangeRight from 'lodash/rangeRight'
import find from 'lodash/find'
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 { exchangeRateChannel, formatUsdValue } from '../lib/currency'
import { createStore, connectElements } from '../lib/redux_helpers.js' import { createStore, connectElements } from '../lib/redux_helpers.js'
import { batchChannel } 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/history_chart' import { createMarketHistoryChart } from '../lib/history_chart'
@ -33,7 +37,7 @@ export const reducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) { function baseReducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'RECEIVED_NEW_ADDRESS_COUNT': { case 'RECEIVED_NEW_ADDRESS_COUNT': {
return Object.assign({}, state, { return Object.assign({}, state, {
@ -127,12 +131,12 @@ function withMissingBlocks (reducer) {
if (!result.blocks || result.blocks.length < 2) return result if (!result.blocks || result.blocks.length < 2) return result
const maxBlock = _.first(result.blocks).blockNumber const maxBlock = first(result.blocks).blockNumber
const minBlock = maxBlock - (result.blocks.length - 1) const minBlock = maxBlock - (result.blocks.length - 1)
return Object.assign({}, result, { return Object.assign({}, result, {
blocks: _.rangeRight(minBlock, maxBlock + 1) blocks: rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => _.find(result.blocks, ['blockNumber', blockNumber]) || { .map((blockNumber) => find(result.blocks, ['blockNumber', blockNumber]) || {
blockNumber, blockNumber,
chainBlockHtml: placeHolderBlock(blockNumber) chainBlockHtml: placeHolderBlock(blockNumber)
}) })
@ -203,7 +207,7 @@ const elements = {
const container = $el[0] const container = $el[0]
if (state.blocksLoading === false) { if (state.blocksLoading === false) {
const blocks = _.map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0]) const blocks = map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0])
listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true }) listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true })
} }
} }
@ -219,11 +223,7 @@ const elements = {
}, },
'[data-selector="chain-block-list"] [data-selector="loading-message"]': { '[data-selector="chain-block-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) { render ($el, state, oldState) {
if (state.blocksLoading) { showLoader(state.blocksLoading, $el)
$el.show()
} else {
$el.hide()
}
} }
}, },
'[data-selector="transactions-list"] [data-selector="error-message"]': { '[data-selector="transactions-list"] [data-selector="error-message"]': {
@ -233,7 +233,7 @@ const elements = {
}, },
'[data-selector="transactions-list"] [data-selector="loading-message"]': { '[data-selector="transactions-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) { render ($el, state, oldState) {
$el.toggle(state.transactionsLoading) showLoader(state.transactionsLoading, $el)
} }
}, },
'[data-selector="transactions-list"]': { '[data-selector="transactions-list"]': {
@ -243,7 +243,7 @@ const elements = {
render ($el, state, oldState) { render ($el, state, oldState) {
if (oldState.transactions === state.transactions) return if (oldState.transactions === state.transactions) return
const container = $el[0] const container = $el[0]
const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) const newElements = map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.identifierHash' }) listMorph(container, newElements, { key: 'dataset.identifierHash' })
} }
}, },

@ -0,0 +1,11 @@
import $ from 'jquery'
$('.dark-mode-changer').click(function () {
if (localStorage.getItem('current-color-mode') === 'dark') {
localStorage.setItem('current-color-mode', 'light')
} else {
localStorage.setItem('current-color-mode', 'dark')
}
// reload each theme switch
document.location.reload(true)
})

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
@ -20,7 +20,7 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { return Object.assign({}, state, {

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
@ -13,7 +13,7 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'RECEIVED_NEW_BLOCK': { case 'RECEIVED_NEW_BLOCK': {
if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) { if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) {

@ -1,5 +1,5 @@
import $ from 'jquery' import $ from 'jquery'
import _ from 'lodash' import omit from 'lodash/omit'
import humps from 'humps' import humps from 'humps'
import numeral from 'numeral' import numeral from 'numeral'
import socket from '../socket' import socket from '../socket'
@ -18,7 +18,7 @@ export const initialState = {
export function reducer (state = initialState, action) { export function reducer (state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'ELEMENTS_LOAD': { case 'ELEMENTS_LOAD': {
return Object.assign({}, state, _.omit(action, 'type')) return Object.assign({}, state, omit(action, 'type'))
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { return Object.assign({}, state, {

@ -0,0 +1,144 @@
import $ from 'jquery'
import omit from 'lodash/omit'
import URI from 'urijs'
import humps from 'humps'
import { subscribeChannel } from '../socket'
import { createStore, connectElements } from '../lib/redux_helpers.js'
export const initialState = {
channelDisconnected: false,
addressHash: null,
newForm: null
}
export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
return Object.assign({}, state, {
channelDisconnected: true
})
}
case 'RECEIVED_VERIFICATION_RESULT': {
if (action.msg.verificationResult === 'ok') {
return window.location.replace(window.location.href.split('/contract_verifications')[0] + '/contracts')
} else {
return Object.assign({}, state, {
newForm: action.msg.verificationResult
})
}
}
default:
return state
}
}
const elements = {
'[data-selector="channel-disconnected-message"]': {
render ($el, state) {
if (state.channelDisconnected) $el.show()
}
},
'[data-page="contract-verification"]': {
render ($el, state) {
if (state.newForm) {
$el.replaceWith(state.newForm)
$('button[data-button-loading="animation"]').click(event => {
$('#loading').removeClass('d-none')
})
$(function () {
$('.js-btn-add-contract-libraries').on('click', function () {
$('.js-smart-contract-libraries-wrapper').show()
$(this).hide()
})
$('.js-smart-contract-form-reset').on('click', function () {
$('.js-contract-library-form-group').removeClass('active')
$('.js-contract-library-form-group').first().addClass('active')
$('.js-smart-contract-libraries-wrapper').hide()
$('.js-btn-add-contract-libraries').show()
$('.js-add-contract-library-wrapper').show()
})
$('.js-btn-add-contract-library').on('click', function () {
let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
if (nextContractLibrary) {
nextContractLibrary.addClass('active')
}
if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) {
$('.js-add-contract-library-wrapper').hide()
}
})
})
return $el
}
return $el
}
}
}
const $contractVerificationPage = $('[data-page="contract-verification"]')
if ($contractVerificationPage.length) {
const store = createStore(reducer)
const addressHash = $('#smart_contract_address_hash').val()
const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true))
store.dispatch({
type: 'PAGE_LOAD',
addressHash,
filter,
beyondPageOne: !!blockNumber
})
connectElements({ store, elements })
const addressChannel = subscribeChannel(`addresses:${addressHash}`)
addressChannel.onError(() => store.dispatch({
type: 'CHANNEL_DISCONNECTED'
}))
addressChannel.on('verification', (msg) => store.dispatch({
type: 'RECEIVED_VERIFICATION_RESULT',
msg: humps.camelizeKeys(msg)
}))
$('button[data-button-loading="animation"]').click(event => {
$('#loading').removeClass('d-none')
})
$(function () {
$('.js-btn-add-contract-libraries').on('click', function () {
$('.js-smart-contract-libraries-wrapper').show()
$(this).hide()
})
$('.js-smart-contract-form-reset').on('click', function () {
$('.js-contract-library-form-group').removeClass('active')
$('.js-contract-library-form-group').first().addClass('active')
$('.js-smart-contract-libraries-wrapper').hide()
$('.js-btn-add-contract-libraries').show()
$('.js-add-contract-library-wrapper').show()
})
$('.js-btn-add-contract-library').on('click', function () {
let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
if (nextContractLibrary) {
nextContractLibrary.addClass('active')
}
if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) {
$('.js-add-contract-library-wrapper').hide()
}
})
})
}

@ -1,6 +1,7 @@
const path = require('path'); const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const { ContextReplacementPlugin } = require('webpack');
const glob = require("glob"); const glob = require("glob");
function transpileViewScript(file) { function transpileViewScript(file) {
@ -74,7 +75,8 @@ const appJs =
}, },
plugins: [ plugins: [
new ExtractTextPlugin('../css/app.css'), new ExtractTextPlugin('../css/app.css'),
new CopyWebpackPlugin([{ from: 'static/', to: '../' }]) new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/)
] ]
} }

@ -35,11 +35,6 @@ config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: t
# Configures the endpoint # Configures the endpoint
config :block_scout_web, BlockScoutWeb.Endpoint, config :block_scout_web, BlockScoutWeb.Endpoint,
instrumenters: [BlockScoutWeb.Prometheus.Instrumenter, SpandexPhoenix.Instrumenter], instrumenters: [BlockScoutWeb.Prometheus.Instrumenter, SpandexPhoenix.Instrumenter],
http: [
protocol_options: [
idle_timeout: 90_000
]
],
url: [ url: [
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") || "/"

@ -16,15 +16,14 @@ port =
config :block_scout_web, BlockScoutWeb.Endpoint, config :block_scout_web, BlockScoutWeb.Endpoint,
http: [ http: [
protocol_options: [
idle_timeout: 90_000
],
port: port || 4000 port: port || 4000
], ],
url: [
scheme: "http",
host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/"
],
https: [ https: [
protocol_options: [
idle_timeout: 90_000
],
port: (port && port + 1) || 4001, port: (port && port + 1) || 4001,
cipher_suite: :strong, cipher_suite: :strong,
certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem", certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem",

@ -20,8 +20,10 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
check_origin: System.get_env("CHECK_ORIGIN") || false, check_origin: System.get_env("CHECK_ORIGIN") || false,
http: [port: System.get_env("PORT")], http: [port: System.get_env("PORT")],
url: [ url: [
scheme: "http", scheme: "https",
port: System.get_env("PORT") port: System.get_env("PORT"),
host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/"
] ]
config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true

@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressChannel do
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Phoenix.View alias Phoenix.View
intercept(["balance_update", "coin_balance", "count", "internal_transaction", "transaction"]) intercept(["balance_update", "coin_balance", "count", "internal_transaction", "transaction", "verification_result"])
def join("addresses:" <> address_hash, _params, socket) do def join("addresses:" <> address_hash, _params, socket) do
{:ok, %{}, assign(socket, :address_hash, address_hash)} {:ok, %{}, assign(socket, :address_hash, address_hash)}
@ -58,6 +58,18 @@ defmodule BlockScoutWeb.AddressChannel do
end end
end end
def handle_out("verification_result", result, socket) do
case result[:result] do
{:ok, _contract} ->
push(socket, "verification", %{verification_result: :ok})
{:noreply, socket}
{:error, result} ->
push(socket, "verification", %{verification_result: result})
{:noreply, socket}
end
end
def handle_out("count", %{count: count}, socket) do def handle_out("count", %{count: count}, socket) do
Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale)

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do :ok <- Chain.check_address_exists(address_hash) do
full_options = paging_options(params) full_options = paging_options(params)
coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options) coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options)
@ -32,7 +32,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
address_coin_balance_path( address_coin_balance_path(
conn, conn,
:index, :index,
address, address_hash,
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end
@ -52,7 +52,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
:error -> :error ->
unprocessable_entity(conn) unprocessable_entity(conn)
{:error, :not_found} -> :not_found ->
not_found(conn) not_found(conn)
end end
end end
@ -64,8 +64,8 @@ defmodule BlockScoutWeb.AddressCoinBalanceController 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(),
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address), validation_count: validation_count(address_hash),
current_path: current_path(conn) current_path: current_path(conn)
) )
end end

@ -8,16 +8,26 @@ defmodule BlockScoutWeb.AddressContractController do
alias Indexer.Fetcher.CoinBalanceOnDemand alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render( render(
conn, conn,
"index.html", "index.html",
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(),
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
:error -> :error ->

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Explorer.Chain.SmartContract alias Explorer.Chain.SmartContract
alias Explorer.SmartContract.{Publisher, Solidity.CodeCompiler, Solidity.CompilerVersion} alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion}
def new(conn, %{"address_id" => address_hash_string}) do def new(conn, %{"address_id" => address_hash_string}) do
changeset = changeset =
@ -16,31 +16,21 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
render(conn, "new.html", render(conn, "new.html",
changeset: changeset, changeset: changeset,
compiler_versions: compiler_versions, compiler_versions: compiler_versions,
evm_versions: CodeCompiler.allowed_evm_versions() evm_versions: CodeCompiler.allowed_evm_versions(),
address_hash: address_hash_string
) )
end end
def create( def create(
conn, conn,
%{ %{
"address_id" => address_hash_string,
"smart_contract" => smart_contract, "smart_contract" => smart_contract,
"external_libraries" => external_libraries "external_libraries" => external_libraries
} }
) do ) do
case Publisher.publish(address_hash_string, smart_contract, external_libraries) do Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn})
{:ok, _smart_contract} ->
redirect(conn, to: address_contract_path(conn, :index, address_hash_string))
{:error, changeset} -> send_resp(conn, 204, "")
{:ok, compiler_versions} = CompilerVersion.fetch_versions()
render(conn, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
evm_versions: CodeCompiler.allowed_evm_versions()
)
end
end end
def parse_optimization_runs(%{"runs" => runs}) do def parse_optimization_runs(%{"runs" => runs}) do

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressController do
alias BlockScoutWeb.AddressView alias BlockScoutWeb.AddressView
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.Chain.Address alias Explorer.Chain.Hash
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Phoenix.View alias Phoenix.View
@ -45,7 +45,7 @@ defmodule BlockScoutWeb.AddressController do
exchange_rate: exchange_rate, exchange_rate: exchange_rate,
total_supply: total_supply, total_supply: total_supply,
tx_count: tx_count, tx_count: tx_count,
validation_count: validation_count(address) validation_count: validation_count(address.hash)
) )
end) end)
@ -69,11 +69,11 @@ defmodule BlockScoutWeb.AddressController do
redirect(conn, to: address_transaction_path(conn, :index, id)) redirect(conn, to: address_transaction_path(conn, :index, id))
end end
def transaction_count(%Address{} = address) do def transaction_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
Chain.total_transactions_sent_by_address(address) Chain.total_transactions_sent_by_address(address_hash)
end end
def validation_count(%Address{} = address) do def validation_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
Chain.address_to_validation_count(address) Chain.address_to_validation_count(address_hash)
end end
end end

@ -16,8 +16,8 @@ defmodule BlockScoutWeb.AddressDecompiledContractController 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(),
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
:error -> :error ->

@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
@ -28,7 +28,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params)) |> Keyword.merge(current_filter(params))
internal_transactions_plus_one = Chain.address_to_internal_transactions(address, full_options) internal_transactions_plus_one = Chain.address_to_internal_transactions(address_hash, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one) {internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
next_page_path = next_page_path =
@ -71,8 +71,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
current_path: current_path(conn), current_path: current_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"], filter: params["filter"],
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
:error -> :error ->

@ -16,8 +16,8 @@ defmodule BlockScoutWeb.AddressLogsController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do :ok <- Chain.check_address_exists(address_hash) do
logs_plus_one = Chain.address_to_logs(address, paging_options(params)) logs_plus_one = Chain.address_to_logs(address_hash, paging_options(params))
{results, next_page} = split_list_by_page(logs_plus_one) {results, next_page} = split_list_by_page(logs_plus_one)
next_page_url = next_page_url =
@ -26,7 +26,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil nil
next_page_params -> next_page_params ->
address_logs_path(conn, :index, address, Map.delete(next_page_params, "type")) address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end end
items = items =
@ -63,8 +63,8 @@ defmodule BlockScoutWeb.AddressLogsController 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(),
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
_ -> _ ->
@ -74,12 +74,12 @@ defmodule BlockScoutWeb.AddressLogsController do
def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do :ok <- Chain.check_address_exists(address_hash) do
topic = String.trim(topic) topic = String.trim(topic)
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic
logs_plus_one = Chain.address_to_logs(address, topic: formatted_topic) logs_plus_one = Chain.address_to_logs(address_hash, topic: formatted_topic)
{results, next_page} = split_list_by_page(logs_plus_one) {results, next_page} = split_list_by_page(logs_plus_one)
@ -89,7 +89,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil nil
next_page_params -> next_page_params ->
address_logs_path(conn, :index, address, Map.delete(next_page_params, "type")) address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end end
items = items =
@ -115,4 +115,6 @@ defmodule BlockScoutWeb.AddressLogsController do
not_found(conn) not_found(conn)
end end
end end
def search_logs(conn, _), do: not_found(conn)
end end

@ -15,16 +15,26 @@ defmodule BlockScoutWeb.AddressReadContractController do
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1] import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
def index(conn, %{"address_id" => address_hash_string}) do def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
necessity_by_association: %{
:contracts_creation_internal_transaction => :optional,
:names => :optional,
:smart_contract => :optional,
:token => :optional,
:contracts_creation_transaction => :optional
}
]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash) do {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render( render(
conn, conn,
"index.html", "index.html",
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(),
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
:error -> :error ->

@ -12,7 +12,7 @@ defmodule BlockScoutWeb.AddressTokenController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [], false) do
tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params)) tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params))
{tokens, next_page} = split_list_by_page(tokens_plus_one) {tokens, next_page} = split_list_by_page(tokens_plus_one)
@ -64,8 +64,8 @@ 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(),
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
:error -> :error ->

@ -85,8 +85,8 @@ 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,
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address) validation_count: validation_count(address_hash)
) )
else else
:error -> :error ->

@ -23,20 +23,22 @@ defmodule BlockScoutWeb.AddressTransactionController do
[token_transfers: :token] => :optional, [token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional, [token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional, [token_transfers: :from_address] => :optional,
[token_transfers: :token_contract_address] => :optional [token_transfers: :token_contract_address] => :optional,
:block => :required
} }
] ]
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
address_options = [necessity_by_association: %{:names => :optional}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash, [:names], false) do {:ok, address} <- Chain.hash_to_address(address_hash, address_options, false) do
options = options =
@transaction_necessity_by_association @transaction_necessity_by_association
|> put_in([:necessity_by_association, :block], :required)
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params)) |> Keyword.merge(current_filter(params))
results_plus_one = Chain.address_to_transactions_with_rewards(address, options) results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options)
{results, next_page} = split_list_by_page(results_plus_one) {results, next_page} = split_list_by_page(results_plus_one)
next_page_url = next_page_url =
@ -95,8 +97,8 @@ defmodule BlockScoutWeb.AddressTransactionController 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(),
filter: params["filter"], filter: params["filter"],
transaction_count: transaction_count(address), transaction_count: transaction_count(address_hash),
validation_count: validation_count(address), validation_count: validation_count(address_hash),
current_path: current_path(conn) current_path: current_path(conn)
) )
else else
@ -108,7 +110,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
end end
end end
def token_transfers_csv(conn, %{"address_id" => address_hash_string}) do def token_transfers_csv(conn, %{"address_id" => address_hash_string}) when is_binary(address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash) do
address address
@ -128,6 +130,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
end end
end end
def token_transfers_csv(conn, _), do: not_found(conn)
def transactions_csv(conn, %{"address_id" => address_hash_string}) do def transactions_csv(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash) do
@ -147,4 +151,6 @@ defmodule BlockScoutWeb.AddressTransactionController do
not_found(conn) not_found(conn)
end end
end end
def transactions_csv(conn, _), do: not_found(conn)
end end

@ -17,7 +17,7 @@ defmodule BlockScoutWeb.AddressValidationController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_or_insert_address_from_hash(address_hash) do {:ok, _} <- Chain.find_or_insert_address_from_hash(address_hash, [], false) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -31,7 +31,7 @@ defmodule BlockScoutWeb.AddressValidationController do
paging_options(params) paging_options(params)
) )
blocks_plus_one = Chain.get_blocks_validated_by_address(full_options, address) blocks_plus_one = Chain.get_blocks_validated_by_address(full_options, address_hash)
{blocks, next_page} = split_list_by_page(blocks_plus_one) {blocks, next_page} = split_list_by_page(blocks_plus_one)
next_page_path = next_page_path =
@ -63,9 +63,6 @@ defmodule BlockScoutWeb.AddressValidationController do
else else
:error -> :error ->
unprocessable_entity(conn) unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end end
end end
@ -78,8 +75,8 @@ defmodule BlockScoutWeb.AddressValidationController do
address: address, address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn), current_path: current_path(conn),
transaction_count: transaction_count(address), transaction_count: transaction_count(address.hash),
validation_count: validation_count(address), validation_count: validation_count(address.hash),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null() exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
) )
else else

@ -3,14 +3,13 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
alias BlockScoutWeb.Chain, as: ChainWeb alias BlockScoutWeb.Chain, as: ChainWeb
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.BlockNumberCache alias Explorer.Chain.Cache.BlockNumber
def getblockreward(conn, params) do def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
{:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number), {:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number),
block_options = [necessity_by_association: %{transactions: :optional}], {:ok, block} <- Chain.number_to_block(block_number) do
{:ok, block} <- Chain.number_to_block(block_number, block_options) do reward = Chain.block_reward(block_number)
reward = Chain.block_reward(block)
render(conn, :block_reward, block: block, reward: reward) render(conn, :block_reward, block: block, reward: reward)
else else
@ -27,7 +26,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
def eth_block_number(conn, params) do def eth_block_number(conn, params) do
id = Map.get(params, "id", 1) id = Map.get(params, "id", 1)
max_block_number = BlockNumberCache.max_number() max_block_number = BlockNumber.max_number()
render(conn, :eth_block_number, number: max_block_number, id: id) render(conn, :eth_block_number, number: max_block_number, id: id)
end end

@ -1,38 +1,10 @@
defmodule BlockScoutWeb.API.RPC.EthController do defmodule BlockScoutWeb.API.RPC.EthController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias Ecto.Type, as: EctoType alias Explorer.EthRPC
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei}
alias Explorer.Etherscan.Logs
@methods %{
"eth_getBalance" => %{
action: :eth_get_balance,
notes: """
the `earliest` parameter will not work as expected currently, because genesis block balances
are not currently imported
"""
},
"eth_getLogs" => %{
action: :eth_get_logs,
notes: """
Will never return more than 1000 log entries.
"""
}
}
@index_to_word %{
0 => "first",
1 => "second",
2 => "third",
3 => "fourth"
}
def methods, do: @methods
def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do
responses = responses(requests) responses = EthRPC.responses(requests)
conn conn
|> put_status(200) |> put_status(200)
@ -40,7 +12,7 @@ defmodule BlockScoutWeb.API.RPC.EthController do
end end
def eth_request(%{body_params: %{"_json" => request}} = conn, _) do def eth_request(%{body_params: %{"_json" => request}} = conn, _) do
[response] = responses([request]) [response] = EthRPC.responses([request])
conn conn
|> put_status(200) |> put_status(200)
@ -59,297 +31,10 @@ defmodule BlockScoutWeb.API.RPC.EthController do
_ -> request _ -> request
end end
[response] = responses([decoded_request]) [response] = EthRPC.responses([decoded_request])
conn conn
|> put_status(200) |> put_status(200)
|> render("response.json", %{response: response}) |> render("response.json", %{response: response})
end end
def eth_get_balance(address_param, block_param \\ nil) do
with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)},
{:block, {:ok, block}} <- {:block, block_param(block_param)},
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do
{:ok, Wei.hex_format(balance)}
else
{:address, :error} ->
{:error, "Query parameter 'address' is invalid"}
{:block, :error} ->
{:error, "Query parameter 'block' is invalid"}
{:balance, {:error, :not_found}} ->
{:error, "Balance not found"}
end
end
def eth_get_logs(filter_options) do
with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options),
{:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options),
{:ok, from_block} <- cast_block(from_block_param),
{:ok, to_block} <- cast_block(to_block_param) do
filter =
address_or_topic_params
|> Map.put(:from_block, from_block)
|> Map.put(:to_block, to_block)
|> Map.put(:allow_non_consensus, true)
{:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)}
else
{:error, message} when is_bitstring(message) ->
{:error, message}
{:error, :empty} ->
{:ok, []}
_ ->
{:error, "Something went wrong."}
end
end
defp render_log(log) do
topics =
Enum.reject(
[log.first_topic, log.second_topic, log.third_topic, log.fourth_topic],
&is_nil/1
)
%{
"address" => to_string(log.address_hash),
"blockHash" => to_string(log.block_hash),
"blockNumber" => Integer.to_string(log.block_number, 16),
"data" => to_string(log.data),
"logIndex" => Integer.to_string(log.index, 16),
"removed" => log.block_consensus == false,
"topics" => topics,
"transactionHash" => to_string(log.transaction_hash),
"transactionIndex" => log.transaction_index,
"transactionLogIndex" => log.index,
"type" => "mined"
}
end
defp cast_block("0x" <> hexadecimal_digits = input) do
case Integer.parse(hexadecimal_digits, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, input <> " is not a valid block number"}
end
end
defp cast_block(integer) when is_integer(integer), do: {:ok, integer}
defp cast_block(_), do: {:error, "invalid block number"}
defp address_or_topic_params(filter_options) do
address_param = Map.get(filter_options, "address")
topics_param = Map.get(filter_options, "topics")
with {:ok, address} <- validate_address(address_param),
{:ok, topics} <- validate_topics(topics_param) do
address_and_topics(address, topics)
end
end
defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"}
defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}}
defp address_and_topics(nil, topics), do: {:ok, topics}
defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)}
defp validate_address(nil), do: {:ok, nil}
defp validate_address(address) do
case Address.cast(address) do
{:ok, address} -> {:ok, address}
:error -> {:error, "invalid address"}
end
end
defp validate_topics(nil), do: {:ok, nil}
defp validate_topics([]), do: []
defp validate_topics(topics) when is_list(topics) do
topics
|> Stream.with_index()
|> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} ->
case cast_topics(topic) do
{:ok, data} ->
with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data)
{:ok, add_operator(with_filter, index)}
:error ->
{:error, "invalid topics"}
end
end)
end
defp add_operator(filters, 0), do: filters
defp add_operator(filters, index) do
Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and")
end
defp cast_topics(topics) when is_list(topics) do
case EctoType.cast({:array, Data}, topics) do
{:ok, data} -> {:ok, Enum.map(data, &to_string/1)}
:error -> :error
end
end
defp cast_topics(topic) do
case Data.cast(topic) do
{:ok, data} -> {:ok, to_string(data)}
:error -> :error
end
end
defp responses(requests) do
Enum.map(requests, fn request ->
with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")},
{:request, {:ok, result}} <- {:request, do_eth_request(request)} do
format_success(result, id)
else
{:id, :error} -> format_error("id is a required field", 0)
{:request, {:error, message}} -> format_error(message, Map.get(request, "id"))
end
end)
end
defp logs_blocks_filter(filter_options) do
with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options},
{:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)},
{:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do
{:ok, number, number}
else
{:filter, filters} ->
from_block = Map.get(filters, "fromBlock", "latest")
to_block = Map.get(filters, "toBlock", "latest")
max_block_number =
if from_block == "latest" || to_block == "latest" do
max_consensus_block_number()
end
pending_block_number =
if from_block == "pending" || to_block == "pending" do
max_non_consensus_block_number(max_block_number)
end
if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do
{:error, :empty}
else
to_block_numbers(from_block, to_block, max_block_number, pending_block_number)
end
{:block, _} ->
{:error, "Invalid Block Hash"}
{:block_hash, _} ->
{:error, "Invalid Block Hash"}
end
end
defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do
actual_pending_block_number = pending_block_number || max_block_number
with {:ok, from} <-
to_block_number(from_block, max_block_number, actual_pending_block_number),
{:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do
{:ok, from, to}
end
end
defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer}
defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0}
defp to_block_number("earliest", _, _), do: {:ok, 0}
defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0}
defp to_block_number("pending", _, pending), do: {:ok, pending}
defp to_block_number("0x" <> number, _, _) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(number, _, _) when is_bitstring(number) do
case Integer.parse(number, 16) do
{integer, ""} -> {:ok, integer}
_ -> {:error, "invalid block number"}
end
end
defp to_block_number(_, _, _), do: {:error, "invalid block number"}
defp max_non_consensus_block_number(max) do
case Chain.max_non_consensus_block_number(max) do
{:ok, number} -> number
_ -> nil
end
end
defp max_consensus_block_number do
case Chain.max_consensus_block_number() do
{:ok, number} -> number
_ -> nil
end
end
defp format_success(result, id) do
%{result: result, id: id}
end
defp format_error(message, id) do
%{error: message, id: id}
end
defp do_eth_request(%{"jsonrpc" => rpc_version}) when rpc_version != "2.0" do
{:error, "invalid rpc version"}
end
defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params})
when is_list(params) do
with {:ok, action} <- get_action(method),
{:correct_arity, true} <-
{:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do
apply(__MODULE__, action, params)
else
{:correct_arity, _} ->
{:error, "Incorrect number of params."}
_ ->
{:error, "Action not found."}
end
end
defp do_eth_request(%{"params" => _params, "method" => _}) do
{:error, "Invalid params. Params must be a list."}
end
defp do_eth_request(_) do
{:error, "Method, params, and jsonrpc, are all required parameters."}
end
defp get_action(action) do
case Map.get(@methods, action) do
%{action: action} ->
{:ok, action}
_ ->
:error
end
end
defp block_param("latest"), do: {:ok, :latest}
defp block_param("earliest"), do: {:ok, :earliest}
defp block_param("pending"), do: {:ok, :pending}
defp block_param(string_integer) when is_bitstring(string_integer) do
case Integer.parse(string_integer) do
{integer, ""} -> {:ok, integer}
_ -> :error
end
end
defp block_param(nil), do: {:ok, :latest}
defp block_param(_), do: :error
end end

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash), {:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction, paging_options) logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs) {logs, next_page} = split_list_by_page(logs)
render(conn, :gettxinfo, %{ render(conn, :gettxinfo, %{

@ -7,8 +7,9 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
def create(conn, params) do def create(conn, params) do
if auth_token(conn) == actual_token() do if auth_token(conn) == actual_token() do
with {:ok, hash} <- validate_address_hash(params["address_hash"]), with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- smart_contract_exists?(hash), :ok <- Chain.check_address_exists(hash),
:ok <- decompiled_contract_exists?(params["address_hash"], params["decompiler_version"]) do {:contract, :not_found} <-
{:contract, Chain.check_decompiled_contract_exists(params["address_hash"], params["decompiler_version"])} do
case Chain.create_decompiled_smart_contract(params) do case Chain.create_decompiled_smart_contract(params) do
{:ok, decompiled_smart_contract} -> {:ok, decompiled_smart_contract} ->
send_resp(conn, :created, Jason.encode!(decompiled_smart_contract)) send_resp(conn, :created, Jason.encode!(decompiled_smart_contract))
@ -29,7 +30,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
:not_found -> :not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
:contract_exists -> {:contract, :ok} ->
send_resp( send_resp(
conn, conn,
:unprocessable_entity, :unprocessable_entity,
@ -41,13 +42,6 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
end end
end end
defp smart_contract_exists?(address_hash) do
case Chain.hash_to_address(address_hash) do
{:ok, _address} -> :ok
_ -> :not_found
end
end
defp validate_address_hash(address_hash) do defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash} {:ok, hash} -> {:ok, hash}
@ -55,13 +49,6 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
end end
end end
defp decompiled_contract_exists?(address_hash, decompiler_version) do
case Chain.decompiled_code(address_hash, decompiler_version) do
{:ok, _} -> :contract_exists
_ -> :ok
end
end
defp auth_token(conn) do defp auth_token(conn) do
case get_req_header(conn, "auth_token") do case get_req_header(conn, "auth_token") do
[token] -> token [token] -> token

@ -4,19 +4,22 @@ defmodule BlockScoutWeb.API.V1.HealthController do
alias Explorer.Chain alias Explorer.Chain
def health(conn, _) do def health(conn, _) do
with {:ok, number, timestamp} <- Chain.last_block_status() do with {:ok, number, timestamp} <- Chain.last_db_block_status(),
send_resp(conn, :ok, result(number, timestamp)) {:ok, cache_number, cache_timestamp} <- Chain.last_cache_block_status() do
send_resp(conn, :ok, result(number, timestamp, cache_number, cache_timestamp))
else else
status -> send_resp(conn, :internal_server_error, error(status)) status -> send_resp(conn, :internal_server_error, error(status))
end end
end end
def result(number, timestamp) do def result(number, timestamp, cache_number, cache_timestamp) do
%{ %{
"healthy" => true, "healthy" => true,
"data" => %{ "data" => %{
"latest_block_number" => to_string(number), "latest_block_number" => to_string(number),
"latest_block_inserted_at" => to_string(timestamp) "latest_block_inserted_at" => to_string(timestamp),
"cache_latest_block_number" => to_string(cache_number),
"cache_latest_block_inserted_at" => to_string(cache_timestamp)
} }
} }
|> Jason.encode!() |> Jason.encode!()

@ -7,8 +7,8 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
def create(conn, params) do def create(conn, params) do
with {:ok, hash} <- validate_address_hash(params["address_hash"]), with {:ok, hash} <- validate_address_hash(params["address_hash"]),
:ok <- smart_contract_exists?(hash), :ok <- Chain.check_address_exists(hash),
:ok <- verified_smart_contract_exists?(hash) do {:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do
external_libraries = fetch_external_libraries(params) external_libraries = fetch_external_libraries(params)
case Publisher.publish(hash, params, external_libraries) do case Publisher.publish(hash, params, external_libraries) do
@ -31,7 +31,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
:not_found -> :not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"})) send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
:contract_exists -> {:contract, :ok} ->
send_resp( send_resp(
conn, conn,
:unprocessable_entity, :unprocessable_entity,
@ -40,13 +40,6 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
end end
end end
defp smart_contract_exists?(address_hash) do
case Chain.hash_to_address(address_hash) do
{:ok, _address} -> :ok
_ -> :not_found
end
end
defp validate_address_hash(address_hash) do defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash} {:ok, hash} -> {:ok, hash}
@ -54,14 +47,6 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
end end
end end
defp verified_smart_contract_exists?(address_hash) do
if Chain.address_hash_to_smart_contract(address_hash) do
:contract_exists
else
:ok
end
end
defp encode(data) do defp encode(data) do
Jason.encode!(data) Jason.encode!(data)
end end

@ -1,8 +1,8 @@
defmodule BlockScoutWeb.APIDocsController do defmodule BlockScoutWeb.APIDocsController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.API.RPC.EthController
alias BlockScoutWeb.Etherscan alias BlockScoutWeb.Etherscan
alias Explorer.EthRPC
def index(conn, _params) do def index(conn, _params) do
conn conn
@ -12,7 +12,7 @@ defmodule BlockScoutWeb.APIDocsController do
def eth_rpc(conn, _params) do def eth_rpc(conn, _params) do
conn conn
|> assign(:documentation, EthController.methods()) |> assign(:documentation, EthRPC.methods())
|> render("eth_rpc.html") |> render("eth_rpc.html")
end end
end end

@ -26,7 +26,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
paging_options(params) paging_options(params)
) )
transactions_plus_one = Chain.block_to_transactions(block, full_options) transactions_plus_one = Chain.block_to_transactions(block.hash, full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one) {transactions, next_page} = split_list_by_page(transactions_plus_one)
@ -89,7 +89,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
:rewards => :optional :rewards => :optional
} }
) do ) do
block_transaction_count = Chain.block_to_transaction_count(block) block_transaction_count = Chain.block_to_transaction_count(block.hash)
render( render(
conn, conn,

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.ChainController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
alias BlockScoutWeb.ChainView alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Supply.RSK
@ -75,6 +75,8 @@ defmodule BlockScoutWeb.ChainController do
end end
end end
def search(conn, _), do: not_found(conn)
def token_autocomplete(conn, %{"q" => term}) when is_binary(term) do def token_autocomplete(conn, %{"q" => term}) when is_binary(term) do
if term == "" do if term == "" do
json(conn, "{}") json(conn, "{}")
@ -95,9 +97,15 @@ defmodule BlockScoutWeb.ChainController do
def chain_blocks(conn, _params) do def chain_blocks(conn, _params) do
if ajax?(conn) do if ajax?(conn) do
blocks = blocks =
[paging_options: %PagingOptions{page_size: 4}] [
paging_options: %PagingOptions{page_size: 4},
necessity_by_association: %{
[miner: :names] => :optional,
:transactions => :optional,
:rewards => :optional
}
]
|> Chain.list_blocks() |> Chain.list_blocks()
|> Repo.preload([[miner: :names], :transactions, :rewards])
|> Enum.map(fn block -> |> Enum.map(fn block ->
%{ %{
chain_block_html: chain_block_html:

@ -30,10 +30,12 @@ defmodule BlockScoutWeb.SmartContractController do
end end
end end
def index(conn, _), do: not_found(conn)
def show(conn, params) do def show(conn, params) do
with true <- ajax?(conn), with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), {:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
{:ok, _address} <- Chain.find_contract_address(address_hash), :ok <- Chain.check_contract_address_exists(address_hash),
outputs = outputs =
Reader.query_function( Reader.query_function(
address_hash, address_hash,
@ -51,7 +53,7 @@ defmodule BlockScoutWeb.SmartContractController do
:error -> :error ->
unprocessable_entity(conn) unprocessable_entity(conn)
{:error, :not_found} -> :not_found ->
not_found(conn) not_found(conn)
_ -> _ ->

@ -43,8 +43,10 @@ defmodule BlockScoutWeb.Tokens.HolderController do
end end
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}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -60,8 +60,10 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
end end
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}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -4,8 +4,10 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
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}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -44,8 +44,10 @@ defmodule BlockScoutWeb.Tokens.TransferController do
end end
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}]
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render( render(
conn, conn,
"index.html", "index.html",

@ -62,15 +62,15 @@ defmodule BlockScoutWeb.TransactionController do
def show(conn, %{"id" => id}) do def show(conn, %{"id" => id}) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id), with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id),
{:ok, %Chain.Transaction{} = transaction} <- Chain.hash_to_transaction(transaction_hash) do :ok <- Chain.check_transaction_exists(transaction_hash) do
if Chain.transaction_has_token_transfers?(transaction.hash) do if Chain.transaction_has_token_transfers?(transaction_hash) do
redirect(conn, to: transaction_token_transfer_path(conn, :index, id)) redirect(conn, to: transaction_token_transfer_path(conn, :index, id))
else else
redirect(conn, to: transaction_internal_transaction_path(conn, :index, id)) redirect(conn, to: transaction_internal_transaction_path(conn, :index, id))
end end
else else
:error -> conn |> put_status(422) |> render("invalid.html", transaction_hash: id) :error -> conn |> put_status(422) |> render("invalid.html", transaction_hash: id)
{:error, :not_found} -> conn |> put_status(404) |> render("not_found.html", transaction_hash: id) :not_found -> conn |> put_status(404) |> render("not_found.html", transaction_hash: id)
end end
end end
end end

@ -10,7 +10,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
{:ok, transaction} <- Chain.hash_to_transaction(hash) do :ok <- Chain.check_transaction_exists(hash) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -24,7 +24,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
paging_options(params) paging_options(params)
) )
internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction, full_options) internal_transactions_plus_one = Chain.transaction_to_internal_transactions(hash, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one) {internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
@ -37,7 +37,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
transaction_internal_transaction_path( transaction_internal_transaction_path(
conn, conn,
:index, :index,
transaction, hash,
Map.delete(next_page_params, "type") Map.delete(next_page_params, "type")
) )
end end
@ -66,7 +66,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
|> put_view(TransactionView) |> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string) |> render("invalid.html", transaction_hash: hash_string)
{:error, :not_found} -> :not_found ->
conn conn
|> put_status(404) |> put_status(404)
|> put_view(TransactionView) |> put_view(TransactionView)

@ -11,7 +11,9 @@ defmodule BlockScoutWeb.TransactionLogController do
def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string),
{:ok, transaction} <- {:ok, transaction} <-
Chain.hash_to_transaction(transaction_hash) do Chain.hash_to_transaction(transaction_hash,
necessity_by_association: %{[to_address: :smart_contract] => :optional}
) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -22,7 +24,7 @@ defmodule BlockScoutWeb.TransactionLogController do
paging_options(params) paging_options(params)
) )
logs_plus_one = Chain.transaction_to_logs(transaction, full_options) logs_plus_one = Chain.transaction_to_logs(transaction_hash, full_options)
{logs, next_page} = split_list_by_page(logs_plus_one) {logs, next_page} = split_list_by_page(logs_plus_one)

@ -19,7 +19,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
:token_transfers => :optional :token_transfers => :optional
} }
) do ) do
internal_transactions = Chain.transaction_to_internal_transactions(transaction) internal_transactions = Chain.transaction_to_internal_transactions(hash)
render( render(
conn, conn,

@ -10,8 +10,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
{:ok, transaction} <- :ok <- Chain.check_transaction_exists(hash) do
Chain.hash_to_transaction(hash) do
full_options = full_options =
Keyword.merge( Keyword.merge(
[ [
@ -24,7 +23,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
paging_options(params) paging_options(params)
) )
token_transfers_plus_one = Chain.transaction_to_token_transfers(transaction, full_options) token_transfers_plus_one = Chain.transaction_to_token_transfers(hash, full_options)
{token_transfers, next_page} = split_list_by_page(token_transfers_plus_one) {token_transfers, next_page} = split_list_by_page(token_transfers_plus_one)
@ -34,7 +33,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
nil nil
next_page_params -> next_page_params ->
transaction_token_transfer_path(conn, :index, transaction, Map.delete(next_page_params, "type")) transaction_token_transfer_path(conn, :index, hash, Map.delete(next_page_params, "type"))
end end
items = items =
@ -62,7 +61,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
|> put_view(TransactionView) |> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string) |> render("invalid.html", transaction_hash: hash_string)
{:error, :not_found} -> :not_found ->
conn conn
|> put_status(404) |> put_status(404)
|> put_view(TransactionView) |> put_view(TransactionView)

@ -4,12 +4,14 @@ defmodule BlockScoutWeb.Notifier do
""" """
alias Absinthe.Subscription alias Absinthe.Subscription
alias BlockScoutWeb.Endpoint alias BlockScoutWeb.{AddressContractVerificationView, Endpoint}
alias Explorer.{Chain, Market, Repo} alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.{Address, InternalTransaction, Transaction} alias Explorer.Chain.{Address, InternalTransaction, Transaction}
alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.SmartContract.{Solidity.CodeCompiler, Solidity.CompilerVersion}
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_with_balance_from_cache()}) Endpoint.broadcast("addresses:new_address", "count", %{count: Chain.count_addresses_with_balance_from_cache()})
@ -24,6 +26,38 @@ defmodule BlockScoutWeb.Notifier do
Enum.each(address_coin_balances, &broadcast_address_coin_balance/1) Enum.each(address_coin_balances, &broadcast_address_coin_balance/1)
end end
def handle_event(
{:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}}
) do
contract_verification_result =
case contract_verification_result do
{:ok, _} = result ->
result
{:error, changeset} ->
{:ok, compiler_versions} = CompilerVersion.fetch_versions()
result =
View.render_to_string(AddressContractVerificationView, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
evm_versions: CodeCompiler.allowed_evm_versions(),
address_hash: address_hash,
conn: conn
)
{:error, result}
end
Endpoint.broadcast(
"addresses:#{address_hash}",
"verification_result",
%{
result: contract_verification_result
}
)
end
def handle_event({:chain_event, :block_rewards, :realtime, rewards}) do def handle_event({:chain_event, :block_rewards, :realtime, rewards}) do
if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do
broadcast_rewards(rewards) broadcast_rewards(rewards)

@ -23,6 +23,7 @@ defmodule BlockScoutWeb.RealtimeEventHandler do
Subscriber.to(:transactions, :realtime) Subscriber.to(:transactions, :realtime)
Subscriber.to(:addresses, :on_demand) Subscriber.to(:addresses, :on_demand)
Subscriber.to(:address_coin_balances, :on_demand) Subscriber.to(:address_coin_balances, :on_demand)
Subscriber.to(:contract_verification_result, :on_demand)
# Does not come from the indexer # Does not come from the indexer
Subscriber.to(:exchange_rate) Subscriber.to(:exchange_rate)
Subscriber.to(:transaction_stats) Subscriber.to(:transaction_stats)

@ -12,8 +12,8 @@ defmodule BlockScoutWeb.Resolvers.Transaction do
end end
end end
def get_by(%Address{} = address, args, _) do def get_by(%Address{hash: address_hash}, args, _) do
address address_hash
|> GraphQL.address_to_transactions_query() |> GraphQL.address_to_transactions_query()
|> Connection.from_query(&Repo.all/1, args, options(args)) |> Connection.from_query(&Repo.all/1, args, options(args))
end end

@ -29,6 +29,12 @@ defmodule BlockScoutWeb.Router do
resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create]) resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create])
end end
scope "/verify_smart_contract" do
pipe_through(:api)
post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create)
end
scope "/api", BlockScoutWeb.API.RPC do scope "/api", BlockScoutWeb.API.RPC do
pipe_through(:api) pipe_through(:api)
@ -166,7 +172,7 @@ defmodule BlockScoutWeb.Router do
resources( resources(
"/contract_verifications", "/contract_verifications",
AddressContractVerificationController, AddressContractVerificationController,
only: [:new, :create], only: [:new],
as: :verify_contract as: :verify_contract
) )
@ -260,6 +266,6 @@ defmodule BlockScoutWeb.Router do
get("/api_docs", APIDocsController, :index) get("/api_docs", APIDocsController, :index)
get("/eth_rpc_api_docs", APIDocsController, :eth_rpc) get("/eth_rpc_api_docs", APIDocsController, :eth_rpc)
get("/:page", PageNotFoundController, :index) get("/*path", PageNotFoundController, :index)
end end
end end

@ -21,7 +21,7 @@
<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 %> <%= if @total_supply do %>
(<%= balance_percentage(@address, @total_supply) %>) <%= balance_percentage(@address, @total_supply) %>
<% end %> <% end %>
</td> </td>
<td class="stakes-td"> <td class="stakes-td">

@ -38,7 +38,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" %>
</tbody> </tbody>
</table> </table>
</div> </div>

@ -74,7 +74,7 @@
<%= if contract?(@address) do %> <%= if contract?(@address) do %>
<%= gettext(">=") %> <%= gettext(">=") %>
<span data-selector="transaction-count"> <span data-selector="transaction-count">
<%= incoming_transaction_count(@address) %> <%= incoming_transaction_count(@address.hash) %>
</span> </span>
<%= gettext("Incoming Transactions") %> <%= gettext("Incoming Transactions") %>
<% else %> <% else %>
@ -146,7 +146,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<img src="data:image/png;base64, <%= qr_code(@address) %>" class="qr-code" alt="qr_code" title="<%= @address %>" /> <img src="data:image/png;base64, <%= qr_code(@address.hash) %>" class="qr-code" alt="qr_code" title="<%= @address %>" />
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"><%= gettext "Close" %></button> <button type="button" class="btn btn-primary" data-dismiss="modal"><%= gettext "Close" %></button>

@ -40,7 +40,9 @@
</div> </div>
</div> </div>
<div data-selector="coin-balances-list" data-items></div> <div data-selector="coin-balances-list" data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= 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 %>

@ -23,12 +23,22 @@
<%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %> <%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
<div class="mb-4"> <div class="mb-4">
<dl class="row"> <dl class="row">
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Contract name:" %></dt> <dt class="col-md-2 text-muted"><%= gettext "Contract name:" %></dt>
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.name %></dd> <dd class="col-md-4"><%= @address.smart_contract.name %></dd>
<div class="d-none d-sm-block d-md-none"></br></br></div>
<div class="d-block d-sm-none"></br></br></div>
<dt class="col-md-2 text-muted"><%= gettext "Optimization enabled" %></dt>
<dd class="col-md-4"><%= format_optimization_text(@address.smart_contract.optimization) %></dd>
</dl> </dl>
<dl class="row"> <dl class="row">
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Compiler version" %></dt> <dt class="col-md-2 text-muted"><%= gettext "Compiler version" %></dt>
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.compiler_version %></dd> <dd class="col-md-4"><%= @address.smart_contract.compiler_version %></dd>
<div class="d-none d-sm-block d-md-none"></br></br></div>
<div class="d-block d-sm-none"></br></br></div>
<%= if @address.smart_contract.optimization && @address.smart_contract.optimization_runs do %>
<dt class="col-md-2 text-muted"><%= gettext "Optimization runs" %></dt>
<dd class="col-md-4"><%= @address.smart_contract.optimization_runs %></dd>
<% end %>
</dl> </dl>
<%= if @address.smart_contract.evm_version do %> <%= if @address.smart_contract.evm_version do %>
<dl class="row"> <dl class="row">
@ -36,35 +46,18 @@
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.evm_version %></dd> <dd class="col-sm-8 col-md-10"><%= @address.smart_contract.evm_version %></dd>
</dl> </dl>
<% end %> <% end %>
<dl class="row">
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Optimization enabled" %></dt>
<dd class="col-sm-8 col-md-10"><%= format_optimization_text(@address.smart_contract.optimization) %></dd>
</dl>
<%= if @address.smart_contract.optimization && @address.smart_contract.optimization_runs do %>
<dl class="row">
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Optimization runs" %></dt>
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.optimization_runs %></dd>
</dl>
<% end %>
<%= if @address.smart_contract.constructor_arguments do %>
<dl class="row">
<dt class="col-sm-4 col-md-2 text-muted"><%= gettext "Constructor arguments" %></dt>
<dd class="col-sm-8 col-md-10"><%= @address.smart_contract.constructor_arguments %></dd>
</dl>
<% end %>
<%= if @address.smart_contract.external_libraries do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "External libraries" %></h3>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= format_smart_contract_abi(@address.smart_contract.abi) %></code>
</pre>
</div>
</section>
<% end %>
</div>
<hr/> <hr/>
<%= if @address.smart_contract.constructor_arguments do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Constructor Arguments" %></h3>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= raw(format_constructor_arguments(@address.smart_contract)) %></code>
</pre>
</div>
</section>
<% end %>
<section> <section>
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Contract source code" %></h3> <h3><%= gettext "Contract source code" %></h3>
@ -109,7 +102,7 @@
</div> </div>
<% {:ok, contract_code} -> %> <% {:ok, contract_code} -> %>
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h2 class="card-title"><%= gettext "Contract Byte Code" %></h2> <h3><%= gettext "Contract Byte Code" %></h3>
<button type="button" class="btn-line" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code"> <button type="button" class="btn-line" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code">
<%= gettext "Copy Contract Byte Code" %> <%= gettext "Copy Contract Byte Code" %>
</button> </button>
@ -120,6 +113,19 @@
<% end %> <% end %>
</section> </section>
<%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
<%= if @address.smart_contract.external_libraries && @address.smart_contract.external_libraries != [] do %>
<section>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "External libraries" %></h3>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= raw(format_external_libraries(@address.smart_contract.external_libraries)) %></code>
</pre>
</div>
</section>
<% end %>
<% end %>
</div> </div>
</div> </div>
</section> </section>

@ -1,9 +1,15 @@
<section class="container new-smart-contract-container"> <section data-page="contract-verification" class="container new-smart-contract-container">
<div data-selector="channel-disconnected-message" style="display: none;">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost" %></a>
</div>
</div>
<div class="new-smart-contract-form"> <div class="new-smart-contract-form">
<h1 class="smart-contract-title"><%= gettext "New Smart Contract Verification" %></h1> <h1 class="smart-contract-title"><%= gettext "New Smart Contract Verification" %></h1>
<%= form_for @changeset, <%= form_for @changeset,
address_verify_contract_path(@conn, :create, @conn.params["address_id"]), address_contract_verification_path(@conn, :create),
[], [],
fn f -> %> fn f -> %>
@ -44,7 +50,7 @@
<div class="smart-contract-form-group-inner-wrapper"> <div class="smart-contract-form-group-inner-wrapper">
<%= label :evm_version, :evm_version, gettext("EVM Version") %> <%= label :evm_version, :evm_version, gettext("EVM Version") %>
<div class="center-column"> <div class="center-column">
<%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", selected: "petersburg", "aria-describedby": "evm-version-help-block" %> <%= select f, :evm_version, @evm_versions, class: "form-control border-rounded", selected: "default", "aria-describedby": "evm-version-help-block" %>
</div> </div>
<div class="smart-contract-form-group-tooltip">The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. <a href="https://forum.poa.network/t/smart-contract-verification-evm-version-details/2318" target="_blank">EVM version details</a>.</div> <div class="smart-contract-form-group-tooltip">The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version. <a href="https://forum.poa.network/t/smart-contract-verification-evm-version-details/2318" target="_blank">EVM version details</a>.</div>
</div> </div>
@ -240,13 +246,13 @@
</span> </span>
<%= gettext("Loading....") %> <%= gettext("Loading....") %>
</button> </button>
<%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-loading": "animation" %> <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation" %>
<%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> <%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %>
<%= <%=
link( link(
gettext("Cancel"), gettext("Cancel"),
class: "btn-no-border", class: "btn-no-border",
to: address_contract_path(@conn, :index, @conn.params["address_id"]) to: address_contract_path(@conn, :index, @address_hash)
) )
%> %>
</div> </div>

@ -66,7 +66,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= 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 %>

@ -11,6 +11,76 @@
) %> ) %>
</h3> </h3>
</dd> </dd>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<%= case decode(@log, @log.transaction) do %>
<% {:error, :contract_not_verified} -> %>
<div class="alert alert-info">
<%= gettext "To see decoded input data, the contract must be verified." %>
<%= case @log.transaction do %>
<% %{to_address: %{hash: hash}} -> %>
<%= gettext "Verify the contract " %><a href="<%= address_verify_contract_path(@conn, :new, hash)%>"><%= gettext "here" %></a>
<% _ -> %>
<%= nil %>
<% end %>
</div>
<% {:error, :could_not_decode} -> %>
<div class="alert alert-danger">
<%= gettext "Failed to decode log data." %>
</div>
<% {:ok, method_id, text, mapping} -> %>
<table summary="Transaction Info" class="table thead-light table-bordered transaction-input-table">
<tr>
<td>Method Id</td>
<td colspan="3"><code>0x<%= method_id %></code></td>
</tr>
<tr>
<td>Call</td>
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<div class="table-responsive text-center">
<table style="color: black;" summary="<%= gettext "Log Data" %>" class="table thead-light table-bordered">
<tr>
<th scope="col"></th>
<th scope="col"><%= gettext "Name" %></th>
<th scope="col"><%= gettext "Type" %></th>
<th scope="col"><%= gettext "Indexed?" %></th>
<th scope="col"><%= gettext "Data" %></th>
<tr>
<%= for {name, type, indexed?, value} <- mapping do %>
<tr>
<th scope="row">
<%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
<% :error -> %>
<%= nil %>
<% copy_text -> %>
<span
aria-label='<%= gettext "Copy Value" %>'
class="btn-copy-ico"
data-clipboard-text="<%= copy_text %>"
data-placement="top"
data-toggle="tooltip"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32.5 32.5" width="32" height="32">
<path fill-rule="evenodd" d="M23.5 20.5a1 1 0 0 1-1-1v-9h-9a1 1 0 0 1 0-2h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-3-7v10a1 1 0 0 1-1 1h-10a1 1 0 0 1-1-1v-10a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm-2 1h-8v8h8v-8z"/>
</svg>
</span>
<% end %>
</th>
<td><%= name %></td>
<td><%= type %></td>
<td><%= indexed? %></td>
<td>
<pre class="transaction-input-text tile"><code><%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %></code></pre>
</td>
</tr>
<% end %>
</table>
</div>
<% _ -> %>
<%= nil %>
<% end %>
<dt class="col-md-2"><%= gettext "Topics" %></dt> <dt class="col-md-2"><%= gettext "Topics" %></dt>
<dd class="col-md-10"> <dd class="col-md-10">
<div class="raw-transaction-log-topics"> <div class="raw-transaction-log-topics">

@ -27,7 +27,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= 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 %>

@ -19,7 +19,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div class="transaction-bottom-panel"> <div class="transaction-bottom-panel">
<div csv-download class="download-all-transactions"> <div csv-download class="download-all-transactions">

@ -21,7 +21,9 @@
</span> </span>
</button> </button>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= 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 %>

@ -65,7 +65,9 @@
</div> </div>
</div> </div>
<div data-items></div> <div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div class="transaction-bottom-panel"> <div class="transaction-bottom-panel">
<div class="download-all-transactions"> <div class="download-all-transactions">

@ -22,7 +22,9 @@
<%= gettext "Something went wrong, click to reload." %> <%= gettext "Something went wrong, click to reload." %>
</span> </span>
</button> </button>
<div data-items data-selector="validations-list"></div> <div data-items data-selector="validations-list">
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> <%= 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 %>

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

Loading…
Cancel
Save