Merge branch 'master' into update-buttons

pull/2671/head
Yegor 5 years ago committed by GitHub
commit c85526dbad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 39
      README.md
  3. 1
      apps/block_scout_web/assets/css/_images-preload.scss
  4. 28
      apps/block_scout_web/assets/css/app.scss
  5. 347
      apps/block_scout_web/assets/css/components/_card.scss
  6. 8
      apps/block_scout_web/assets/css/components/_custom_tooltips_block_details.scss
  7. 351
      apps/block_scout_web/assets/css/components/_dashboard-banner.scss
  8. 10
      apps/block_scout_web/assets/css/components/_erc721_token_image_container.scss
  9. 3
      apps/block_scout_web/assets/css/components/_network-selector.scss
  10. 12
      apps/block_scout_web/assets/css/components/_tile.scss
  11. 7
      apps/block_scout_web/assets/css/components/_transaction.scss
  12. 58
      apps/block_scout_web/assets/css/theme/_dai_variables.scss
  13. 1
      apps/block_scout_web/assets/css/theme/_variables-non-critical.scss
  14. 1
      apps/block_scout_web/assets/css/theme/_variables.scss
  15. 7
      apps/block_scout_web/assets/css/theme/_xusdt_variables-non-critical.scss
  16. 97
      apps/block_scout_web/assets/css/theme/_xusdt_variables.scss
  17. 6
      apps/block_scout_web/assets/js/lib/try_api.js
  18. 12
      apps/block_scout_web/assets/js/lib/try_eth_api.js
  19. 6
      apps/block_scout_web/assets/package-lock.json
  20. 2
      apps/block_scout_web/assets/package.json
  21. 14
      apps/block_scout_web/assets/static/images/dai_logo.svg
  22. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/circle-xusdt.svg
  23. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.svg
  24. 3
      apps/block_scout_web/assets/static/images/xusdt-logo-footer.svg
  25. 4
      apps/block_scout_web/assets/static/images/xusdt-logo-top.svg
  26. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
  27. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
  28. 28
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  29. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
  30. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  31. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  32. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
  33. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  34. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  35. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  36. 8
      apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
  37. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
  38. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  39. 4
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex
  40. 37
      apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
  41. 49
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  42. 48
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
  43. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
  44. 6
      apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
  45. 9
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  46. 23
      apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
  47. 9
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  48. 6
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  49. 4
      apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex
  50. 1451
      apps/block_scout_web/priv/gettext/default.pot
  51. 1460
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  52. 20
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  53. 4
      apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
  54. 4
      apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs
  55. 2
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  56. 4
      apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs
  57. 8
      apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs
  58. 47
      apps/block_scout_web/test/block_scout_web/views/address_contract_view_test.exs
  59. 44
      apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
  60. 2
      apps/block_scout_web/test/block_scout_web/views/block_view_test.exs
  61. 7
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  62. 22
      apps/explorer/config/config.exs
  63. 13
      apps/explorer/lib/explorer/application.ex
  64. 136
      apps/explorer/lib/explorer/chain.ex
  65. 2
      apps/explorer/lib/explorer/chain/address.ex
  66. 5
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  67. 168
      apps/explorer/lib/explorer/chain/cache/block_count.ex
  68. 93
      apps/explorer/lib/explorer/chain/cache/block_number.ex
  69. 4
      apps/explorer/lib/explorer/chain/cache/blocks.ex
  70. 47
      apps/explorer/lib/explorer/chain/cache/net_version.ex
  71. 174
      apps/explorer/lib/explorer/chain/cache/transaction_count.ex
  72. 4
      apps/explorer/lib/explorer/chain/cache/transactions.ex
  73. 217
      apps/explorer/lib/explorer/chain/map_cache.ex
  74. 42
      apps/explorer/lib/explorer/chain/ordered_cache.ex
  75. 39
      apps/explorer/lib/explorer/chain/smart_contract.ex
  76. 4
      apps/explorer/lib/explorer/chain/supply/exchange_rate.ex
  77. 2
      apps/explorer/lib/explorer/chain/supply/rsk.ex
  78. 44
      apps/explorer/lib/explorer/chain/transaction.ex
  79. 54
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  80. 89
      apps/explorer/lib/explorer/chain_spec/poa/importer.ex
  81. 34
      apps/explorer/lib/explorer/exchange_rates/source.ex
  82. 41
      apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex
  83. 52
      apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex
  84. 12
      apps/explorer/lib/explorer/exchange_rates/source/token_bridge.ex
  85. 26
      apps/explorer/test/explorer/chain/cache/block_count_test.exs
  86. 50
      apps/explorer/test/explorer/chain/cache/block_number_test.exs
  87. 4
      apps/explorer/test/explorer/chain/cache/blocks_test.exs
  88. 26
      apps/explorer/test/explorer/chain/cache/transaction_count_test.exs
  89. 24
      apps/explorer/test/explorer/chain_test.exs
  90. 57
      apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs
  91. 59
      apps/explorer/test/explorer/exchange_rates/source/coin_market_cap_test.exs
  92. 49
      apps/explorer/test/explorer/exchange_rates/source/token_bridge_test.exs
  93. 3
      apps/explorer/test/support/data_case.ex
  94. 1806
      apps/explorer/test/support/fixture/exchange_rates/coin_gecko.json
  95. 9
      apps/indexer/lib/indexer/block/fetcher.ex
  96. 2
      apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
  97. 105
      docs/env-variables.md

@ -1,10 +1,13 @@
## Current
### Features
- [#2671](https://github.com/poanetwork/blockscout/pull/2671) - fixed buttons color at smart contract section
- [#2679](https://github.com/poanetwork/blockscout/pull/2679) - added fixed height for card chain blocks and card chain transactions
- [#2678](https://github.com/poanetwork/blockscout/pull/2678) - fixed dashboard banner height bug
- [#2672](https://github.com/poanetwork/blockscout/pull/2672) - added new theme for xUSDT
- [#2663](https://github.com/poanetwork/blockscout/pull/2663) - Fetch address counters in parallel
### Fixes
- [#2682](https://github.com/poanetwork/blockscout/pull/2682) - Use Task.start instead of Task.async in caches
### Chore

@ -16,9 +16,9 @@ BlockScout provides a comprehensive, easy-to-use interface for users to view, co
See our [project documentation](https://poanetwork.github.io/blockscout) for detailed information and setup instructions.
Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here.
Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here.
You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout).
You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout).
## About BlockScout
@ -28,23 +28,24 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc
## Supported Projects
| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** |
|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------|
| [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) |
| [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) |
| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) |
| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) |
| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) |
| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) |
| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) |
| | | [SpringChain](https://explorer.springrole.com/) |
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) |
| | | [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).
| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** | **Additional Chains using BlockScout** |
|--------------------------------------------------------|-------------------------------------------------------|------------------------------------------------------|----------------------------------------------------------------|
| [Callisto](https://blockscout.com/callisto/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) | [Celo Testnet](https://alfajores-blockscout.celo-testnet.org/) |
| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | [Matic Testnet](https://explorer.testnet2.matic.network/) |
| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [LUKSO L14 Testnet](https://blockscout.com/lukso/l14) | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | |
| [POA Core Network](https://blockscout.com/poa/core) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) | |
| [RSK](https://blockscout.com/rsk/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | |
| [xDai Chain](https://blockscout.com/poa/dai) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) | |
| | | [PIRL](http://pirl.es/) | |
| | | [SafeChain](https://explorer.safechain.io) | |
| | | [SpringChain](https://explorer.springrole.com/) | |
| | | [Tenda](https://tenda.network) | |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) | |
| | | [GoJoy Chain](https://gojoychain.com/) | |
| | | [Xerom](https://blocks.xerom.org/) | |
Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).
## Getting Started

@ -13,4 +13,5 @@ body:after {
url(/images/network-selector-icons/ropsten-testnet.svg)
url(/images/network-selector-icons/xdai-chain.svg)
url(/images/network-selector-icons/lukso-l14-testnet.svg)
url(/images/network-selector-icons/circle-xusdt.svg)
};

@ -1,12 +1,10 @@
@import "./mixins";
/* Phoenix flash messages */
.alert:empty {
display: none;
display: none;
}
/* This file is for your main application css. */
// Font Awesome
@ -15,10 +13,13 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "fa-brands";
@import "fa-regular";
@import "fa-solid";
// Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/mixins";
@import "theme/variables";
@import "node_modules/bootstrap/scss/root";
@import "node_modules/bootstrap/scss/reboot";
@import "node_modules/bootstrap/scss/grid";
@ -37,6 +38,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "node_modules/bootstrap/scss/utilities/position";
@import "node_modules/bootstrap/scss/utilities/borders";
@import "node_modules/bootstrap/scss/progress";
// Bootstrap Components
@import "node_modules/bootstrap/scss/alert";
@import "node_modules/bootstrap/scss/badge";
@ -48,10 +50,13 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "node_modules/bootstrap/scss/pagination";
@import "node_modules/bootstrap/scss/tables";
@import "node_modules/bootstrap/scss/transitions";
// Code highlight
@import "node_modules/highlight.js/styles/default";
//Custom theme
@import "theme/fonts";
// Custom SCSS
@import "layout";
@import "typography";
@ -109,18 +114,15 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/radio_big";
@import "components/btn_no_border";
@import "components/custom_tooltips_block_details";
@import "components/_erc721_token_image_container";
@import "theme/dark-theme";
:export {
dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color;
dashboardLineColorMarket: $dashboard-line-color-market;
dashboardLineColorPrice: $dashboard-line-color-price;
primary: $primary;
secondary: $secondary;
darkprimary: $dark-primary;
darksecondary: $dark-secondary;
dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color;
dashboardLineColorMarket: $dashboard-line-color-market;
dashboardLineColorPrice: $dashboard-line-color-price;
primary: $primary;
secondary: $secondary;
darkprimary: $dark-primary;
darksecondary: $dark-secondary;
}

@ -7,214 +7,245 @@ $card-background-1: $primary !default;
$card-background-1-text-color: #fff !default;
$card-tab-icon-color: #20b760 !default;
$card-tab-icon-color-active: #fff !default;
.card {
background-color: $card-background-color;
border-radius: $card-default-border-radius;
border: none;
box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5);
margin-bottom: $common-container-margin;
.block-details-row {
flex-direction: row;
background-color: $card-background-color;
border-radius: $card-default-border-radius;
border: none;
box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5);
margin-bottom: $common-container-margin;
.block-details-row {
flex-direction: row;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
.block-detail-el {
& + .block-detail-el {
@include media-breakpoint-down(sm) {
flex-direction: column;
}
.block-detail-el {
&+.block-detail-el {
@include media-breakpoint-down(sm) {
margin-top: 6px;
}
}
margin-top: 6px;
}
}
}
}
}
.card-background-1 {
background-color: $card-background-1;
background-color: $card-background-1;
color: $card-background-1-text-color;
a:not(.dropdown-item),
a:not(.dropdown-item):hover {
color: $card-background-1-text-color;
a:not(.dropdown-item),
a:not(.dropdown-item):hover {
color: $card-background-1-text-color;
}
}
}
.card-header {
background: transparent;
border-bottom: 1px solid $base-border-color;
padding: $card-vertical-padding $card-horizontal-padding;
&-tabs {
margin: (-$card-spacer-y) (-$card-spacer-x);
}
background: transparent;
border-bottom: 1px solid $base-border-color;
padding: $card-vertical-padding $card-horizontal-padding;
&-tabs {
margin: (-$card-spacer-y) (-$card-spacer-x);
}
}
.card-title {
font-size: 18px;
font-weight: normal;
line-height: 1.2rem;
margin-bottom: 2rem;
&.lg-card-title {
@media (max-width: 374px) {
font-size: 13px;
}
}
&.margin-bottom-md {
margin-bottom: 25px;
}
&.margin-bottom-sm {
margin-bottom: 15px;
}
&.margin-bottom-xs {
margin-bottom: 10px;
}
&.margin-bottom-0 {
margin-bottom: 0;
font-size: 18px;
font-weight: normal;
line-height: 1.2rem;
margin-bottom: 2rem;
&.lg-card-title {
@media (max-width: 374px) {
font-size: 13px;
}
.card-title-container & {
line-height: 1.2;
margin: 0;
@include media-breakpoint-down(sm) {
margin-bottom: 25px;
}
}
&.margin-bottom-md {
margin-bottom: 25px;
}
&.margin-bottom-sm {
margin-bottom: 15px;
}
&.margin-bottom-xs {
margin-bottom: 10px;
}
&.margin-bottom-0 {
margin-bottom: 0;
}
.card-title-container & {
line-height: 1.2;
margin: 0;
@include media-breakpoint-down(sm) {
margin-bottom: 25px;
}
}
}
.card-subtitle {
color: #333;
font-size: 12px;
font-weight: normal;
line-height: 1.2;
margin: 0 0 30px;
&.margin-bottom-0 {
margin-bottom: 0;
}
color: #333;
font-size: 12px;
font-weight: normal;
line-height: 1.2;
margin: 0 0 30px;
&.margin-bottom-0 {
margin-bottom: 0;
}
}
.card-title-container {
align-items: center;
display: flex;
justify-content: space-between;
padding: 25px $card-horizontal-padding;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
align-items: center;
display: flex;
justify-content: space-between;
padding: 25px $card-horizontal-padding;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
}
.card-title-controls {
align-items: center;
display: flex;
justify-content: flex-end;
@include media-breakpoint-down(sm) {
flex-direction: column;
align-items: center;
display: flex;
justify-content: flex-end;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
.card-title-control {
margin-right: 20px;
&:last-child {
margin-right: 0;
}
.card-title-control {
margin-right: 20px;
&:last-child {
margin-right: 0;
}
@include media-breakpoint-down(sm) {
margin-bottom: 20px;
margin-right: 0;
&:last-child {
margin-bottom: 0;
}
}
@include media-breakpoint-down(sm) {
margin-bottom: 20px;
margin-right: 0;
&:last-child {
margin-bottom: 0;
}
}
}
}
.card-body {
padding: $card-horizontal-padding;
padding: $card-horizontal-padding;
}
.card-body-flex-column-space-between {
display: flex;
flex-direction: column;
justify-content: space-between;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card-server-response-body {
max-height: 400px;
overflow-y: auto;
max-height: 400px;
overflow-y: auto;
}
.card-chain-blocks {
height: auto;
[class*="col-"]:last-child {
.tile {
margin-bottom: 0;
}
min-height: 233px;
max-height: auto;
[class*="col-"]:last-child {
.tile {
margin-bottom: 0;
}
}
}
.card-chain-transactions {
height: auto;
.tile {
margin-bottom: 0;
}
min-height: 664px;
max-height: auto;
.tile {
margin-bottom: 0;
}
}
.card-tabs {
align-items: center;
border-top-left-radius: $card-default-border-radius;
border-top-right-radius: $card-default-border-radius;
border-bottom: 1px solid $base-border-color;
display: flex;
justify-content: flex-start;
overflow: hidden;
@include media-breakpoint-down(md) {
flex-direction: column;
}
align-items: center;
border-top-left-radius: $card-default-border-radius;
border-top-right-radius: $card-default-border-radius;
border-bottom: 1px solid $base-border-color;
display: flex;
justify-content: flex-start;
overflow: hidden;
@include media-breakpoint-down(md) {
flex-direction: column;
}
}
.card-tab {
align-items: center;
background-color: $card-background-color;
color: #333;
cursor: pointer;
display: flex;
font-size: 14px;
font-weight: normal;
height: 70px;
padding: 0 25px;
text-align: center;
transition: $transition-cont;
&:hover {
background-color: rgba($card-tab-active, .15);
color: $card-tab-active;
text-decoration: none;
align-items: center;
background-color: $card-background-color;
color: #333;
cursor: pointer;
display: flex;
font-size: 14px;
font-weight: normal;
height: 70px;
padding: 0 25px;
text-align: center;
transition: $transition-cont;
&:hover {
background-color: rgba($card-tab-active, .15);
color: $card-tab-active;
text-decoration: none;
}
@include media-breakpoint-down(md) {
display: none;
width: 100%;
}
.fa-check-circle {
color: $card-tab-icon-color;
margin-left: 6px;
}
&.active {
background-color: $card-tab-active;
color: #fff;
cursor: default;
text-decoration: none;
.fa-check-circle {
color: $card-tab-icon-color-active;
}
@include media-breakpoint-down(md) {
cursor: pointer;
display: flex;
order: -1;
&::after {
border-bottom: 0;
border-left: 0.3em solid transparent;
border-right: 0.3em solid transparent;
border-top: 0.3em solid;
content: "";
display: inline-block;
height: 0;
margin-left: 10px;
width: 0;
}
&.noCaret::after {
display: none;
width: 100%;
}
.fa-check-circle {
color: $card-tab-icon-color;
margin-left: 6px;
}
}
&.active {
background-color: $card-tab-active;
color: #fff;
cursor: default;
text-decoration: none;
.fa-check-circle {
color: $card-tab-icon-color-active;
}
@include media-breakpoint-down(md) {
cursor: pointer;
display: flex;
order: -1;
&::after {
border-bottom: 0;
border-left: 0.3em solid transparent;
border-right: 0.3em solid transparent;
border-top: 0.3em solid;
content: "";
display: inline-block;
height: 0;
margin-left: 10px;
width: 0;
}
&.noCaret::after {
display: none;
}
}
}
}
}
}

@ -10,7 +10,7 @@
position: absolute;
width: 50%;
background-color: white;
color: #5c34a2;
color: black;
text-align: center;
border-radius: 6px;
left: 25%;
@ -22,7 +22,7 @@
margin-bottom: 15px;
opacity: 0;
transition: opacity 0.5s;
@include media-breakpoint-down(sm) {
@include media-breakpoint-down(lg) {
left: 60%;
}
}
@ -60,9 +60,9 @@
margin-bottom: 15px;
opacity: 0;
transition: opacity 0.5s;
@include media-breakpoint-down(sm) {
padding: 9px 10px;
@include media-breakpoint-down(lg) {
bottom: 100%;
padding: 9px 10px;
}
}

@ -1,7 +1,10 @@
$dashboard-banner-gradient-start: $primary !default;
$dashboard-banner-gradient-end: lighten( $dashboard-banner-gradient-start, 5%) !default;
$dashboard-banner-network-plain-container-background-color: lighten( $dashboard-banner-gradient-end, 5%) !default;
$dashboard-line-color-price: lighten( $dashboard-banner-gradient-end, 5%) !default;
$dashboard-banner-gradient-end: lighten(
$dashboard-banner-gradient-start,
5%
) !default;
$dashboard-banner-network-plain-container-background-color: lighten($dashboard-banner-gradient-end, 5%) !default;
$dashboard-line-color-price: lighten($dashboard-banner-gradient-end, 5%) !default;
$dashboard-line-color-market: $secondary !default;
$dashboard-stats-item-label-color: #fff !default;
$dashboard-stats-item-value-color: rgba(#fff, 0.8) !default;
@ -10,192 +13,218 @@ $dashboard-banner-chart-legend-value-color: $dashboard-stats-item-value-color !d
$dashboard-stats-item-border-color: $primary !default;
$dashboard-banner-network-plain-container-height: 205px;
$dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !default;
.dashboard-banner-container {
@include gradient-container();
margin-bottom: 3rem;
overflow: hidden;
padding: 0;
position: relative;
height: 249px;
@include media-breakpoint-down(sm) {
height: auto;
}
@include media-breakpoint-down(md) {
height: auto;
}
@include gradient-container();
margin-bottom: 3rem;
overflow: hidden;
padding: 0;
position: relative;
height: 249px;
@include media-breakpoint-down(sm) {
height: auto;
}
@include media-breakpoint-down(md) {
height: auto;
}
}
.dashboard-banner {
display: flex;
justify-content: space-between;
position: relative;
z-index: 9;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
display: flex;
justify-content: space-between;
position: relative;
z-index: 9;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
}
.dashboard-banner-network-graph {
flex-grow: 1;
padding: 15px 0 0 0;
@include media-breakpoint-down(md) {
display: flex;
flex-direction: column;
padding-top: 20px;
}
flex-grow: 1;
padding: 15px 0 0 0;
@include media-breakpoint-down(md) {
display: flex;
flex-direction: column;
padding-top: 20px;
}
}
.dashboard-banner-chart {
flex-grow: 1;
margin: 0 0 35px 0;
max-width: 350px;
flex-grow: 1;
margin: 0 0 35px 0;
max-width: 350px;
position: relative;
@include media-breakpoint-down(md) {
flex-grow: 0;
margin-bottom: 20px;
margin-top: auto;
max-width: 100%;
}
> canvas {
max-height: 100%;
max-width: 100%;
width: 100%;
}
}
.dashboard-banner-chart-legend {
display: flex;
grid-template-columns: 1fr 1fr;
padding-bottom: 12px;
.dashboard-banner-chart-legend-item {
padding-bottom: 3px;
padding-left: 12px;
padding-top: 3px;
position: relative;
padding-right: 60px;
@include media-breakpoint-down(md) {
flex-grow: 0;
margin-bottom: 20px;
margin-top: auto;
max-width: 100%;
display: flex;
flex-direction: row;
}
>canvas {
max-height: 100%;
max-width: 100%;
width: 100%;
@media (max-width: 599px) {
padding-top: 0;
padding-bottom: 0;
flex-direction: column;
}
}
.dashboard-banner-chart-legend {
display: flex;
/* grid-template-columns: 1fr 1fr; */
padding-bottom: 12px;
.dashboard-banner-chart-legend-item {
padding-bottom: 3px;
padding-left: 12px;
padding-top: 3px;
position: relative;
padding-right: 60px;
@include media-breakpoint-down(md) {
display: flex;
flex-direction: row;
}
@media (max-width: 599px) {
padding-top: 0;
padding-bottom: 0;
flex-direction: column;
}
&::before {
border-radius: 2px;
content: "";
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 4px;
}
&:nth-child(1)::before {
background-color: $dashboard-line-color-price;
}
&:nth-child(2)::before {
background-color: $dashboard-line-color-market;
}
&::before {
border-radius: 2px;
content: "";
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 4px;
}
.dashboard-banner-chart-legend-label {
color: $dashboard-banner-chart-legend-label-color;
display: block;
font-size: 12px;
font-weight: 600;
line-height: 1.2;
margin: 0 0 5px;
@media (max-width: 374px) {
position: relative;
top: -2px;
}
@include media-breakpoint-down(md) {
margin: 0 5px 0 0;
}
&:nth-child(1)::before {
background-color: $dashboard-line-color-price;
}
.dashboard-banner-chart-legend-value {
color: $dashboard-banner-chart-legend-value-color;
display: block;
font-size: 12px;
font-weight: normal;
line-height: 1.2;
&:nth-child(2)::before {
background-color: $dashboard-line-color-market;
}
}
.dashboard-banner-chart-legend-label {
color: $dashboard-banner-chart-legend-label-color;
display: block;
font-size: 12px;
font-weight: 600;
line-height: 1.2;
margin: 0 0 5px;
@media (max-width: 374px) {
position: relative;
top: -2px;
}
@include media-breakpoint-down(md) {
margin: 0 5px 0 0;
}
}
.dashboard-banner-chart-legend-value {
color: $dashboard-banner-chart-legend-value-color;
display: block;
font-size: 12px;
font-weight: normal;
line-height: 1.2;
}
}
.dashboard-banner-network-plain-container {
align-items: center;
align-self: flex-end;
align-items: center;
align-self: flex-end;
background-color: $dashboard-banner-network-plain-container-background-color;
border-top-left-radius: 10px;
display: flex;
height: $dashboard-banner-network-plain-container-height;
justify-content: center;
margin: 45px 0 0 30px;
max-width: 100%;
padding: 30px 0 30px 60px;
width: 750px;
position: relative;
@include media-breakpoint-down(lg) {
margin-top: 15px;
width: 550px;
}
@include media-breakpoint-down(md) {
border-top-right-radius: 10px;
height: auto;
justify-content: flex-start;
margin-left: 0;
max-width: 100%;
padding: 20px 0 20px 20px;
width: 250px;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2);
}
@include media-breakpoint-down(sm) {
width: 100%;
}
&::after {
background-color: $dashboard-banner-network-plain-container-background-color;
border-top-left-radius: 10px;
display: flex;
bottom: 0;
content: "";
display: block;
height: $dashboard-banner-network-plain-container-height;
justify-content: center;
margin: 45px 0 0 30px;
max-width: 100%;
padding: 30px 0 30px 60px;
width: 750px;
position: relative;
@include media-breakpoint-down(lg) {
margin-top: 50px;
width: 550px;
}
left: 0;
position: absolute;
width: 9999px;
z-index: -1;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2);
border-top-left-radius: 10px;
@include media-breakpoint-down(md) {
border-top-right-radius: 10px;
height: auto;
justify-content: flex-start;
margin-left: 0;
max-width: 100%;
padding: 20px 0 20px 20px;
width: 250px;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2);
}
@include media-breakpoint-down(sm) {
width: 100%;
}
&::after {
background-color: $dashboard-banner-network-plain-container-background-color;
bottom: 0;
content: "";
display: block;
height: $dashboard-banner-network-plain-container-height;
left: 0;
position: absolute;
width: 9999px;
z-index: -1;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2);
border-top-left-radius: 10px;
@include media-breakpoint-down(md) {
display: none;
}
display: none;
}
}
}
.dashboard-banner-network-stats {
column-gap: 25px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
@include media-breakpoint-down(lg) {
grid-template-columns: 1fr 1fr;
row-gap: 20px;
}
@include media-breakpoint-down(md) {
grid-template-columns: 1fr;
row-gap: 20px;
}
@include media-breakpoint-down(sm) {
column-gap: 10px;
grid-template-columns: 1fr 1fr;
}
@include stats-item( $dashboard-stats-item-border-color, $dashboard-stats-item-label-color, $dashboard-stats-item-value-color);
.dashboard-banner-network-stats-item {
@media (max-width: 374px) {
padding-left: calc(0.6rem + 4px);
padding-right: 0.5rem;
}
column-gap: 25px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
@include media-breakpoint-down(lg) {
grid-template-columns: 1fr 1fr;
row-gap: 20px;
}
@include media-breakpoint-down(md) {
grid-template-columns: 1fr;
row-gap: 20px;
}
@include media-breakpoint-down(sm) {
column-gap: 10px;
grid-template-columns: 1fr 1fr;
}
@include stats-item($dashboard-stats-item-border-color, $dashboard-stats-item-label-color, $dashboard-stats-item-value-color);
.dashboard-banner-network-stats-item {
@media (max-width: 374px) {
padding-left: calc(0.6rem + 4px);
padding-right: 0.5rem;
}
.dashboard-banner-network-stats-value {
@media (max-width: 374px) {
font-size: 0.9rem;
}
}
.dashboard-banner-network-stats-value {
@media (max-width: 374px) {
font-size: 0.9rem;
}
}
}

@ -1,10 +0,0 @@
/* ERC721 image block */
.erc721-image {
display: flex;
justify-content: center;
}
.erc721-image img {
height: 80px;
}
/* ERC721 image block end */

@ -280,6 +280,9 @@ $network-selector-item-icon-dimensions: 30px !default;
&-lukso-l14-testnet {
background-image: url(/images/network-selector-icons/lukso-l14-testnet.svg)
}
&-xusdt-chain {
background-image: url(/images/network-selector-icons/circle-xusdt.svg)
}
}
.network-selector-item-title {

@ -562,3 +562,15 @@ $cube-quantity: 5;
transform: scale(0);
}
}
.dark-block-loader {
width: auto;
height: 15px;
background-color: #e2e5ec;
margin-bottom: 5px;
border-radius: 4px;
}
.dark-theme-applied .dark-block-loader {
background-color: #313355;
}

@ -43,4 +43,11 @@
text-decoration: underline;
}
}
}
.block-detail-number {
width: 25%;
@include media-breakpoint-down(sm) {
width: 60%;
}
}

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

@ -1,5 +1,6 @@
@import "theme/base_variables";
@import "neutral_variables-non-critical";
// @import "xusdt_variables-non-critical";
// @import "dai_variables-non-critical";
// @import "ethereum_classic_variables-non-critical";
// @import "ethereum_variables-non-critical";

@ -1,5 +1,6 @@
@import "theme/base_variables";
@import "neutral_variables";
// @import "xusdt_variables";
// @import "dai_variables";
// @import "ethereum_classic_variables";
// @import "ethereum_variables";

@ -0,0 +1,7 @@
// general
$primary: #2b9f7a;
$secondary: #20745a;
$tertiary: #fff;
$additional-font: #fff;
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -0,0 +1,97 @@
// general
$primary: #2b9f7a;
$secondary: #20745a;
$tertiary: #fff;
$additional-font: #fff;
$tile-body-a-color: $primary;
$tile-type-block-color: $primary;
$tile-type-progress-bar-color: $primary;
a.tile-title { color: $primary !important; }
// footer
$footer-background-color: #282d31;
$footer-title-color: #fff;
$footer-text-color: $additional-font;
$footer-item-disc-color: $secondary;
$footer-social-icon-color: $secondary;
// dashboard
$dashboard-line-color-price: #fff; // price left border
$dashboard-banner-chart-legend-label-color: #fff;
$dashboard-stats-item-label-color: $dashboard-banner-chart-legend-label-color;
$dashboard-banner-chart-legend-value-color: #fff; // chart labels
$dashboard-stats-item-value-color: #fff; // stat values
$dashboard-stats-item-border-color: $secondary; // stat border
$dashboard-banner-gradient-start: $primary; // gradient begin
$dashboard-banner-gradient-end: #289371; // gradient end
$dashboard-banner-network-plain-container-background-color: #2ea780; // stats bg
// navigation
.navbar { box-shadow: 0px 0px 30px 0px rgba(21, 53, 80, 0.12); } // header shadow
$dropdown-menu-item-hover-color: $primary !default;
$dropdown-menu-item-hover-background: rgba($primary, .1) !default;
$header-icon-color-hover: $primary;
$header-icon-border-color-hover: $primary;
// buttons
$btn-line-bg: #fff; // button bg
$btn-line-color: $primary; // button border and font color && hover bg color
$btn-copy-color: $primary; // btn copy
$btn-qr-color: $primary; // btn qr-code
$btn-address-card-icon-color: $primary; // btn address color
//links & tile
$tile-body-a-color: $primary;
$tile-type-block-color: $primary;
$tile-type-progress-bar-color: $primary;
a.tile-title { color: $primary !important; }
// card
$card-background-1: $primary;
$card-tab-active: $primary;
.layout-container {
.dashboard-banner-container {
background-image: linear-gradient(
to bottom,
$dashboard-banner-gradient-start,
$dashboard-banner-gradient-end
);
}
}
// Badges
$badge-neutral-color: $primary;
$badge-neutral-background-color: rgba($primary, .1);
$api-text-monospace-color: $primary;
// Tokens dropdown
.token-balance-dropdown[aria-labelledby="dropdown-tokens"] {
.dropdown-items .dropdown-item:hover {
color: $primary !important;
}
}
// Dark theme
$dark-primary: #2b9f7a;
$dark-secondary: #20745a;
$dark-primary-alternate: #2b9f7a;
.dark-theme-applied .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before {
background-color: white!important;
}
.dark-theme-applied .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before {
background-color: $primary!important;
}
.dark-theme-applied .tile .tile-body a, .dark-theme-applied .tile span[data-address-hash] {
color: #2b9f7a;
}

@ -55,6 +55,10 @@ function handleSuccess (query, xhr, clickedButton) {
clickedButton.prop('disabled', false)
}
function dropDomain (url) {
return new URL(url).pathname
}
// Show 'Try it out' UI for a module/action.
$('button[data-selector*="btn-try-api"]').click(event => {
const clickedButton = $(event.target)
@ -124,7 +128,7 @@ $('button[data-try-api-ui-button-type="execute"]').click(event => {
}
$.ajax({
url: `/api${query}`,
url: dropDomain(composeRequestUrl(query)),
success: (_data, _status, xhr) => {
handleSuccess(query, xhr, clickedButton)
},

@ -1,7 +1,8 @@
import $ from 'jquery'
function composeCurlCommand (data) {
return `curl -H "content-type: application/json" -X POST --data '${JSON.stringify(data)}'`
const url = $('[data-endpoint-url]').attr('data-endpoint-url')
return `curl -H "content-type: application/json" -X POST --data '${JSON.stringify(data)}' ${url}`
}
function handleResponse (data, xhr, clickedButton) {
@ -43,6 +44,10 @@ function parseInput (input) {
}
}
function dropDomain (url) {
return new URL(url).pathname
}
$('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
const clickedButton = $(event.target)
const module = clickedButton.attr('data-module')
@ -50,7 +55,6 @@ $('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
const inputs = $(`input[data-selector="${module}-${action}-try-api-ui"]`)
const params = $.map(inputs, parseInput)
const formData = wrapJsonRpc(action, params)
console.log(formData)
const loadingText = '<span class="loading-spinner-small mr-2"><span class="loading-spinner-block-1"></span><span class="loading-spinner-block-2"></span></span> Loading...'
clickedButton.prop('disabled', true)
@ -60,8 +64,10 @@ $('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
clickedButton.html(loadingText)
}
const url = $('[data-endpoint-url]').attr('data-endpoint-url')
$.ajax({
url: '/api/eth_rpc',
url: dropDomain(url),
type: 'POST',
data: JSON.stringify(formData),
dataType: 'json',

@ -6958,9 +6958,9 @@
}
},
"lodash": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA=="
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash.assign": {
"version": "4.2.0",

@ -30,7 +30,7 @@
"highlightjs-solidity": "^1.0.6",
"humps": "^2.0.1",
"jquery": "^3.4.0",
"lodash": "^4.17.13",
"lodash": "^4.17.15",
"moment": "^2.22.1",
"nanomorph": "^5.1.3",
"numeral": "^2.0.6",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<path fill="#12AC97" fill-rule="evenodd" d="M0 30V0h30v30H0z"/>
<path fill="#FFF" fill-rule="evenodd" d="M17.172 15L23 22.001h-4.344.775c-.497 0-.926-.229-1.223-.569l-.015.012-2.654-3.187h-.011a.7.7 0 0 0-.528-.243.7.7 0 0 0-.528.243h-.012l-2.653 3.187-.015-.012c-.297.34-.726.569-1.223.569h.774-4.344L12.828 15 6.999 8h3.569l.001-.001c.497 0 .926.229 1.223.569l.014-.012 2.652 3.185h.012c.13.147.315.245.53.245.215 0 .4-.098.53-.245h.011l2.652-3.185.015.012A1.618 1.618 0 0 1 19.432 8h3.569l-5.829 7z"/>
</svg>

After

Width:  |  Height:  |  Size: 584 B

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<path fill="#12AC97" fill-rule="evenodd" d="M30 14.999V30H0V0h30v14.999z"/>
<path fill="#FFF" fill-rule="evenodd" d="M17.172 14.999l5.828 7h-4.344v-.001h.775c-.497 0-.926-.228-1.223-.568l-.015.012-2.655-3.188h-.019a.681.681 0 0 0-.519-.246.681.681 0 0 0-.519.246h-.019l-2.655 3.188-.015-.012c-.297.34-.726.568-1.223.568h.775v.001H7l5.828-7-5.828-7h3.569c.497 0 .926.228 1.223.568l.015-.011 2.651 3.184h.019a.682.682 0 0 0 .523.252.682.682 0 0 0 .523-.252h.018l2.652-3.184.015.011c.297-.34.726-.568 1.223-.568H23l-5.828 7z"/>
<path fill="#202D6A" fill-rule="evenodd" d="M0 30V0h30v30H0z"/>
<path fill="#15F9BB" fill-rule="evenodd" d="M17.172 15L23 22.001h-4.344.775c-.497 0-.926-.229-1.223-.569l-.015.012-2.654-3.187h-.011a.7.7 0 0 0-.528-.243.7.7 0 0 0-.528.243h-.012l-2.653 3.187-.015-.012c-.297.34-.726.569-1.223.569h.774-4.344L12.828 15 6.999 8h3.569l.001-.001c.497 0 .926.229 1.223.569l.014-.012 2.652 3.185h.012c.13.147.315.245.53.245.215 0 .4-.098.53-.245h.011l2.652-3.185.015.012A1.618 1.618 0 0 1 19.432 8h3.569l-5.829 7z"/>
</svg>

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 587 B

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="101" height="36">
<path fill="#FFF" fill-rule="evenodd" d="M29.079 27.848l-10.126 6.457c-3.496 2.231-3.496 2.231-6.989.003L1.81 27.833C.003 26.553.003 24.511.003 24.511V11.464s0-2.222 1.823-3.337l10.138-6.465c3.493-2.225 3.493-2.225 6.992.005l10.123 6.455c1.833 1.177 1.831 3.338 1.831 3.338v13.052s.002 2.139-1.831 3.336zM19.45 11.883c-.429 0-.8.204-1.057.508l-.013-.01-2.294 2.85h-.01a.611.611 0 0 1-.45.201.608.608 0 0 1-.45-.201h-.027l-2.294-2.85-.012.01a1.381 1.381 0 0 0-1.058-.508H8.697l5.042 6.264-5.042 6.264h3.088c.43 0 .801-.204 1.058-.509l.013.011 2.281-2.834h.019a.608.608 0 0 1 .47-.23c.192 0 .356.093.469.23h.003l2.282 2.834.012-.011c.258.305.629.509 1.058.509h3.088l-5.041-6.264 5.041-6.264H19.45zM99.961 30.953l-2.563-3.675h-.028l.018.206c.033.39.05.748.05 1.072v2.397h-.774v-4.611h1.034l2.556 3.655h.021a19.955 19.955 0 0 1-.029-.528 16.461 16.461 0 0 1-.021-.712v-2.415h.781v4.611h-1.045zM97.22 20.801h-2.952V8.776h-3.78V6.199H101v2.577h-3.78v12.025zm-6.64 10.152l-.521-1.281h-1.992l-.51 1.281h-.898l1.949-4.63h.927l1.95 4.63h-.905zm-1.234-3.186a7.578 7.578 0 0 1-.148-.397 7.769 7.769 0 0 1-.13-.398 6.502 6.502 0 0 1-.281.849l-.471 1.202h1.519l-.489-1.256zm-7.989-6.966h-3.941V6.199h4.37c2.272 0 4.037.63 5.294 1.888 1.257 1.259 1.885 3.016 1.885 5.274 0 2.403-.652 4.244-1.957 5.523-1.304 1.278-3.188 1.917-5.651 1.917zm.581-12.065h-1.571v9.508h1.266c2.844 0 4.266-1.601 4.266-4.804 0-3.136-1.32-4.704-3.961-4.704zm-3.57 18.463a5.145 5.145 0 0 0-.558-.194 2.277 2.277 0 0 0-.618-.083c-.49 0-.875.153-1.155.46-.281.308-.421.731-.421 1.271 0 .567.135.994.405 1.284.269.291.66.436 1.171.436.221 0 .435-.02.641-.059a8.21 8.21 0 0 0 .645-.15v.647a4.36 4.36 0 0 1-1.39.205c-.763 0-1.348-.205-1.757-.613-.409-.409-.613-.995-.613-1.756 0-.479.099-.899.297-1.258a2 2 0 0 1 .861-.827c.376-.191.817-.287 1.323-.287a3.63 3.63 0 0 1 1.475.297l-.306.627zm-8.746-15.851c.264.19.887.531 1.871 1.024 1.301.652 2.193 1.307 2.676 1.962.482.656.723 1.46.723 2.412 0 1.319-.452 2.357-1.356 3.116-.905.759-2.163 1.139-3.776 1.139-1.485 0-2.799-.293-3.942-.879v-2.876c.94.439 1.735.749 2.386.929.65.179 1.245.269 1.785.269.647 0 1.144-.13 1.49-.389.346-.26.519-.646.519-1.159 0-.286-.076-.541-.229-.764a2.598 2.598 0 0 0-.671-.644c-.295-.206-.897-.536-1.804-.989-.851-.419-1.489-.822-1.914-1.208a4.513 4.513 0 0 1-1.019-1.349c-.254-.512-.381-1.112-.381-1.797 0-1.292.418-2.308 1.252-3.047.835-.739 1.989-1.108 3.461-1.108.724 0 1.414.09 2.071.269.657.18 1.344.433 2.062.759l-.952 2.407c-.743-.319-1.357-.542-1.843-.669a5.687 5.687 0 0 0-1.433-.19c-.558 0-.987.137-1.285.41-.299.273-.448.629-.448 1.069 0 .273.061.511.181.714.121.203.313.399.576.589zm-7.969 14.994h.851v3.964h2.207v.647h-3.058v-4.611zm-1.071-5.99c-.87.433-1.898.649-3.085.649-1.79 0-3.18-.481-4.171-1.443-.99-.962-1.485-2.279-1.485-3.95V6.199h2.942v8.939c0 1.126.216 1.951.648 2.477.431.526 1.145.789 2.142.789.965 0 1.665-.264 2.099-.794.435-.529.653-1.36.653-2.492V6.199h2.942v9.449c0 1.078-.23 2.024-.691 2.836-.46.813-1.125 1.435-1.994 1.868zm-6.7 10.601h-.905l-.521-1.281h-1.992l-.51 1.281h-.898l1.95-4.63h.926l1.95 4.63zm-2.286-3.583a8.04 8.04 0 0 1-.131-.398 6.502 6.502 0 0 1-.281.849l-.471 1.202h1.519l-.489-1.256a7.792 7.792 0 0 1-.147-.397zm-4.811-6.569l-2.237-3.825-2.247 3.825h-3.295l3.609-5.703-3.428-5.463h3.295l2.066 3.556 2.085-3.556h3.294l-3.465 5.463 3.627 5.703h-3.304zm-6.277 9.578c.338 0 .587-.057.747-.17a.534.534 0 0 0 .241-.458.55.55 0 0 0-.221-.439c-.148-.119-.452-.261-.913-.425-.475-.17-.81-.365-1.005-.583a1.145 1.145 0 0 1-.292-.789c0-.385.154-.688.463-.909.309-.22.724-.331 1.244-.331.499 0 .996.097 1.49.291l-.271.614c-.463-.172-.877-.258-1.24-.258-.276 0-.485.053-.628.159a.503.503 0 0 0-.213.422.54.54 0 0 0 .085.307.837.837 0 0 0 .282.241c.13.076.366.176.706.3.382.141.662.272.841.394.178.122.309.26.392.413.083.154.125.335.125.543 0 .41-.168.731-.503.965-.335.233-.797.35-1.387.35-.589 0-1.071-.081-1.447-.243v-.713c.238.099.49.177.758.234.267.056.516.085.746.085zm7.306-3.391h-1.533v3.965h-.852v-3.965h-1.533v-.646h3.918v.646zm10.991-.369c.319.185.479.477.479.877a.982.982 0 0 1-.235.672c-.157.179-.383.291-.677.337v.032c.366.061.635.181.807.361.172.18.258.423.258.73 0 .415-.163.739-.49.973-.327.235-.781.352-1.363.352h-1.882v-4.611h1.547c.717 0 1.236.092 1.556.277zm-2.251 3.7h.905c.356 0 .626-.061.807-.181.182-.121.273-.313.273-.573 0-.24-.093-.417-.278-.533-.185-.116-.467-.173-.845-.173h-.862v1.46zm0-2.072h.82c.356 0 .616-.05.78-.15.164-.1.246-.27.246-.509 0-.217-.088-.374-.265-.471-.178-.096-.458-.145-.843-.145h-.738v1.275zm13.135-1.268h-2.096v1.255h1.964v.631h-1.964v1.448h2.096v.64h-2.948v-4.611h2.948v.637zm11.896 1.249h2.402v-1.886h.856v4.611h-.856v-2.079h-2.402v2.079h-.852v-4.611h.852v1.886zm12.572 2.725h-.852v-4.611h.852v4.611z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="101" height="36">
<path fill="#12AC97" fill-rule="evenodd" d="M29.079 27.848l-10.126 6.457c-3.496 2.231-3.496 2.231-6.989.003L1.81 27.833C.003 26.553.003 24.511.003 24.511V11.464s0-2.222 1.823-3.337l10.138-6.465c3.493-2.225 3.493-2.225 6.992.005l10.123 6.455c1.833 1.177 1.831 3.338 1.831 3.338v13.052s.002 2.139-1.831 3.336zM19.45 11.883c-.429 0-.8.204-1.057.508l-.013-.01-2.294 2.85h-.01a.611.611 0 0 1-.45.201.608.608 0 0 1-.45-.201h-.027l-2.294-2.85-.012.01a1.381 1.381 0 0 0-1.058-.508H8.697l5.042 6.264-5.042 6.264h3.088c.43 0 .801-.204 1.058-.509l.013.011 2.281-2.834h.019a.608.608 0 0 1 .47-.23c.192 0 .356.093.469.23h.003l2.282 2.834.012-.011c.258.305.629.509 1.058.509h3.088l-5.041-6.264 5.041-6.264H19.45z"/>
<path fill="#333" fill-rule="evenodd" d="M99.961 30.953l-2.563-3.675h-.028l.018.206c.033.39.05.748.05 1.072v2.397h-.774v-4.611h1.034l2.556 3.655h.021a19.955 19.955 0 0 1-.029-.528 16.461 16.461 0 0 1-.021-.712v-2.415h.781v4.611h-1.045zM97.22 20.801h-2.952V8.776h-3.78V6.199H101v2.577h-3.78v12.025zm-6.64 10.152l-.521-1.281h-1.992l-.51 1.281h-.898l1.949-4.63h.927l1.95 4.63h-.905zm-1.234-3.186a7.578 7.578 0 0 1-.148-.397 7.769 7.769 0 0 1-.13-.398 6.502 6.502 0 0 1-.281.849l-.471 1.202h1.519l-.489-1.256zm-7.989-6.966h-3.941V6.199h4.37c2.272 0 4.037.63 5.294 1.888 1.257 1.259 1.885 3.016 1.885 5.274 0 2.403-.652 4.244-1.957 5.523-1.304 1.278-3.188 1.917-5.651 1.917zm.581-12.065h-1.571v9.508h1.266c2.844 0 4.266-1.601 4.266-4.804 0-3.136-1.32-4.704-3.961-4.704zm-3.57 18.463a5.145 5.145 0 0 0-.558-.194 2.277 2.277 0 0 0-.618-.083c-.49 0-.875.153-1.155.46-.281.308-.421.731-.421 1.271 0 .567.135.994.405 1.284.269.291.66.436 1.171.436.221 0 .435-.02.641-.059a8.21 8.21 0 0 0 .645-.15v.647a4.36 4.36 0 0 1-1.39.205c-.763 0-1.348-.205-1.757-.613-.409-.409-.613-.995-.613-1.756 0-.479.099-.899.297-1.258a2 2 0 0 1 .861-.827c.376-.191.817-.287 1.323-.287a3.63 3.63 0 0 1 1.475.297l-.306.627zm-8.746-15.851c.264.19.887.531 1.871 1.024 1.301.652 2.193 1.307 2.676 1.962.482.656.723 1.46.723 2.412 0 1.319-.452 2.357-1.356 3.116-.905.759-2.163 1.139-3.776 1.139-1.485 0-2.799-.293-3.942-.879v-2.876c.94.439 1.735.749 2.386.929.65.179 1.245.269 1.785.269.647 0 1.144-.13 1.49-.389.346-.26.519-.646.519-1.159 0-.286-.076-.541-.229-.764a2.598 2.598 0 0 0-.671-.644c-.295-.206-.897-.536-1.804-.989-.851-.419-1.489-.822-1.914-1.208a4.513 4.513 0 0 1-1.019-1.349c-.254-.512-.381-1.112-.381-1.797 0-1.292.418-2.308 1.252-3.047.835-.739 1.989-1.108 3.461-1.108.724 0 1.414.09 2.071.269.657.18 1.344.433 2.062.759l-.952 2.407c-.743-.319-1.357-.542-1.843-.669a5.687 5.687 0 0 0-1.433-.19c-.558 0-.987.137-1.285.41-.299.273-.448.629-.448 1.069 0 .273.061.511.181.714.121.203.313.399.576.589zm-7.969 14.994h.851v3.964h2.207v.647h-3.058v-4.611zm-1.071-5.99c-.87.433-1.898.649-3.085.649-1.79 0-3.18-.481-4.171-1.443-.99-.962-1.485-2.279-1.485-3.95V6.199h2.942v8.939c0 1.126.216 1.951.648 2.477.431.526 1.145.789 2.142.789.965 0 1.665-.264 2.099-.794.435-.529.653-1.36.653-2.492V6.199h2.942v9.449c0 1.078-.23 2.024-.691 2.836-.46.813-1.125 1.435-1.994 1.868zm-6.7 10.601h-.905l-.521-1.281h-1.992l-.51 1.281h-.898l1.95-4.63h.926l1.95 4.63zm-2.286-3.583a8.04 8.04 0 0 1-.131-.398 6.502 6.502 0 0 1-.281.849l-.471 1.202h1.519l-.489-1.256a7.792 7.792 0 0 1-.147-.397zm-4.811-6.569l-2.237-3.825-2.247 3.825h-3.295l3.609-5.703-3.428-5.463h3.295l2.066 3.556 2.085-3.556h3.294l-3.465 5.463 3.627 5.703h-3.304zm-6.277 9.578c.338 0 .587-.057.747-.17a.534.534 0 0 0 .241-.458.55.55 0 0 0-.221-.439c-.148-.119-.452-.261-.913-.425-.475-.17-.81-.365-1.005-.583a1.145 1.145 0 0 1-.292-.789c0-.385.154-.688.463-.909.309-.22.724-.331 1.244-.331.499 0 .996.097 1.49.291l-.271.614c-.463-.172-.877-.258-1.24-.258-.276 0-.485.053-.628.159a.503.503 0 0 0-.213.422.54.54 0 0 0 .085.307.837.837 0 0 0 .282.241c.13.076.366.176.706.3.382.141.662.272.841.394.178.122.309.26.392.413.083.154.125.335.125.543 0 .41-.168.731-.503.965-.335.233-.797.35-1.387.35-.589 0-1.071-.081-1.447-.243v-.713c.238.099.49.177.758.234.267.056.516.085.746.085zm7.306-3.391h-1.533v3.965h-.852v-3.965h-1.533v-.646h3.918v.646zm10.991-.369c.319.185.479.477.479.877a.982.982 0 0 1-.235.672c-.157.179-.383.291-.677.337v.032c.366.061.635.181.807.361.172.18.258.423.258.73 0 .415-.163.739-.49.973-.327.235-.781.352-1.363.352h-1.882v-4.611h1.547c.717 0 1.236.092 1.556.277zm-2.251 3.7h.905c.356 0 .626-.061.807-.181.182-.121.273-.313.273-.573 0-.24-.093-.417-.278-.533-.185-.116-.467-.173-.845-.173h-.862v1.46zm0-2.072h.82c.356 0 .616-.05.78-.15.164-.1.246-.27.246-.509 0-.217-.088-.374-.265-.471-.178-.096-.458-.145-.843-.145h-.738v1.275zm13.135-1.268h-2.096v1.255h1.964v.631h-1.964v1.448h2.096v.64h-2.948v-4.611h2.948v.637zm11.896 1.249h2.402v-1.886h.856v4.611h-.856v-2.079h-2.402v2.079h-.852v-4.611h.852v1.886zm12.572 2.725h-.852v-4.611h.852v4.611z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.AddressCoinBalanceView
@ -60,12 +60,14 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(conn, "index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash),
transaction_count: transaction_count,
validation_count: validation_count,
current_path: current_path(conn)
)
else

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.AddressContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -20,14 +20,16 @@ defmodule BlockScoutWeb.AddressContractController do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
:error ->

@ -69,6 +69,34 @@ defmodule BlockScoutWeb.AddressController do
redirect(conn, to: address_transaction_path(conn, :index, id))
end
def transaction_and_validation_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
transaction_count_task =
Task.async(fn ->
transaction_count(address_hash)
end)
validation_count_task =
Task.async(fn ->
validation_count(address_hash)
end)
[transaction_count_task, validation_count_task]
|> Task.yield_many(:timer.seconds(30))
|> Enum.map(fn {_task, res} ->
case res do
{:ok, result} ->
result
{:exit, reason} ->
raise "Query fetching address counters terminated: #{inspect(reason)}"
nil ->
raise "Query fetching address counters timed out."
end
end)
|> List.to_tuple()
end
def transaction_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
Chain.total_transactions_sent_by_address(address_hash)
end

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.AddressDecompiledContractController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
@ -10,14 +10,16 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_decompiled_contract_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
:error ->

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.InternalTransactionView
@ -63,6 +63,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
@ -71,8 +73,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
current_path: current_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
:error ->

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.AddressLogsController do
Manages events logs tab.
"""
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.AddressLogsView
@ -56,6 +56,8 @@ defmodule BlockScoutWeb.AddressLogsController do
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
@ -63,8 +65,8 @@ defmodule BlockScoutWeb.AddressLogsController do
current_path: current_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
_ ->

@ -12,7 +12,7 @@ defmodule BlockScoutWeb.AddressReadContractController do
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
def index(conn, %{"address_id" => address_hash_string}) do
address_options = [
@ -27,14 +27,16 @@ defmodule BlockScoutWeb.AddressReadContractController do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
:error ->

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.AddressTokenController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain, only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
alias BlockScoutWeb.AddressTokenView
@ -57,6 +57,8 @@ defmodule BlockScoutWeb.AddressTokenController do
def index(conn, %{"address_id" => address_hash_string} = _params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
@ -64,8 +66,8 @@ defmodule BlockScoutWeb.AddressTokenController do
current_path: current_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
:error ->

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain,
only: [next_page_params: 3, paging_options: 1, split_list_by_page: 1]
@ -77,6 +77,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
{:ok, token_hash} <- Chain.string_to_address_hash(token_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash),
{:ok, token} <- Chain.token_from_address_hash(token_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
@ -85,8 +87,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: current_path(conn),
token: token,
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash)
transaction_count: transaction_count,
validation_count: validation_count
)
else
:error ->

@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.TransactionView
@ -96,6 +96,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
@ -103,8 +105,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
transaction_count: transaction_count(address_hash),
validation_count: validation_count(address_hash),
transaction_count: transaction_count,
validation_count: validation_count,
current_path: current_path(conn)
)
else

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressValidationController do
"""
use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.AddressController, only: [transaction_and_validation_count: 1]
import BlockScoutWeb.Chain,
only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
@ -69,14 +69,16 @@ defmodule BlockScoutWeb.AddressValidationController do
def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.find_or_insert_address_from_hash(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn),
transaction_count: transaction_count(address.hash),
validation_count: validation_count(address.hash),
transaction_count: transaction_count,
validation_count: validation_count,
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
)
else

@ -26,7 +26,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
def eth_block_number(conn, params) do
id = Map.get(params, "id", 1)
max_block_number = BlockNumber.max_number()
max_block_number = BlockNumber.get_max()
render(conn, :eth_block_number, number: max_block_number, id: id)
end

@ -66,7 +66,7 @@
</button>
</div>
<div class="tile tile-muted mb-4">
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="solidity"><%= for {line, number} <- contract_lines_with_index(@address.smart_contract.contract_source_code) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
<pre class="pre-scrollable line-numbers" data-activate-highlight><code class="solidity"><%= for {line, number} <- contract_lines_with_index(@address.smart_contract.contract_source_code, @address.smart_contract.inserted_at) do %><div data-line-number="<%= number %>"><%= line %></div><% end %></code></pre>
</div>
</section>

@ -4,9 +4,9 @@
<h3 class="api-doc-list-item-title"><%= @action %></h3>
<p class="api-doc-list-item-contents"><%= raw @info.notes %></p>
<span class="api-doc-list-item-query api-text-monospace api-text-monospace-background btn"
data-clipboard-text="curl -X POST --data '{&quot;id&quot;:0,&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;method&quot;: &quot;<%= @action %>&quot;, params: []}'"
data-clipboard-text="curl -X POST --data '{&quot;id&quot;:0,&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;method&quot;: &quot;<%= @action %>&quot;,&quot;params&quot;: []}'"
>
curl -X POST --data '{"id":0,"jsonrpc":"2.0","method": "<%= @action %>", params: []}'
curl -X POST --data '{"id":0,"jsonrpc":"2.0","method": "<%= @action %>", "params": []}'
</span>
<p class="api-doc-list-item-text">
<div class="tile tile-muted p-1">

@ -4,24 +4,18 @@
<!-- Block Details -->
<div class="card card-mr-50-md">
<div class="card-body">
<h1 class="card-title" data-test="detail_type" >
<h1 class="card-title" data-test="detail_type">
<%= gettext("%{block_type} Details", block_type: block_type(@block)) %>
</h1>
<!-- Block Height -->
<h3 data-test="block_detail_number" style="width: 25%;" data-toggle="tooltip" data-placement="top" title="" data-original-title="The block number in which transactions were recorded.">
<h3 class="block-detail-number" data-test="block_detail_number" data-toggle="tooltip" data-placement="top" title="" data-original-title="The block number in which transactions were recorded.">
<%= if block_type(@block) == "Block" do %>
<%= gettext("Block Height: %{height}", height: @block.number) %> <%= if @block.number == 0, do: "- " <> gettext("Genesis Block")%>
<% else %>
<%= gettext("%{block_type} Height:", block_type: block_type(@block)) %>
<%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %>
<% end %>
</h3>
<div class="d-flex justify-content-start text-muted block-details-row">
<!-- # of Transactions -->
<span class="mr-4 block-detail-el"> <%= gettext "%{count} Transactions", count: @block_transaction_count %> </span>
@ -36,18 +30,13 @@
</div>
<hr>
<!-- Block details description list -->
<!-- Block Hash -->
<dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Hash" %></dt>
<dd class="col-sm-9"><a class="transaction__link" data-toggle="tooltip" data-placement="top" title="" data-original-title="The SHA256 hash of the block."><%= to_string(@block.hash) %></a></dd>
</dl>
<%= unless @block.number == 0 do %>
<!-- Parent Hash -->
@ -68,7 +57,7 @@
<!-- Difficulty value -->
<dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Difficulty" %></dt>
<dd class="col-sm-9" >
<dd class="col-sm-9">
<span data-toggle="tooltip" data-placement="top" title="" data-original-title="Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)."><%= @block.difficulty |> BlockScoutWeb.Cldr.Number.to_string! %></span>
</dd>
</dl>
@ -95,6 +84,9 @@
<%= link(
gettext("Position %{index}", index: index),
class: "transaction__link",
"data-toggle": "tooltip",
"data-placement": "top" ,
"data-original-title": "Index position(s) of referenced stale blocks." ,
"data-test": "uncle_link",
"data-uncle-hash": to_string(relation.uncle_hash),
to: block_path(@conn, :show, relation.uncle_hash)
@ -106,11 +98,11 @@
<!-- Otherwise it will be displayed in its own block -->
<%= if show_reward?(@block.rewards) do %>
<dl class="row" >
<dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Gas Used" %></dt>
<dd class="col-sm-9">
<span><%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %></span>
<span class="text-muted" data-toggle="tooltip" data-placement="top" title="" data-original-title="Total gas used by all transactions in this block (% used / limit).">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</span>
<span data-toggle="tooltip" data-placement="top" title="" data-original-title="Total gas used by all transactions in this block (limit)."><%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %></span>
<span class="text-muted" data-toggle="tooltip" data-placement="top" title="" data-original-title="Total gas used by all transactions in this block (% used).">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</span>
</dt>
</dl>
<dl class="row mb-0">
@ -148,17 +140,13 @@
</div>
<!-- Validator Reward or Gas-->
<div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0" >
<div class="card flex-grow-1 ml-0 ml-md-5 ml-lg-0">
<div class="card-body card-body-flex-column-space-between">
<%= if show_reward?(@block.rewards) do %>
<h2 class="card-title balance-card-title"><%= gettext "Block Rewards" %></h2>
<div class="text-right tooltipCustom">
<span class="tooltiptextTopR"> Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees. </span>
<div class="text-right" style="margin-left: 50%;" data-toggle="tooltip" data-placement="top" title="" data-original-title="Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees.">
<%= for block_reward <- @block.rewards do %>
<p class="address-current-balance "><%= block_reward_text(block_reward) %> <span class="text-muted"><%= format_wei_value(block_reward.reward, :ether) %></span></p>
<p class="address-current-balance"><%= block_reward_text(block_reward) %> <span class="text-muted"><%= format_wei_value(block_reward.reward, :ether) %></span></p>
<% end %>
</div>
<% else %>
@ -169,7 +157,6 @@
<span class="text-muted">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</span>
</h3>
<p class="address-current-balance"><%= @block.gas_limit |> BlockScoutWeb.Cldr.Number.to_string! %><%= gettext "Gas Limit" %></p>
</div>
<% end %>
</div>

@ -91,13 +91,50 @@
<%= gettext "Something went wrong, click to reload." %>
</span>
</button>
<div hidden data-selector="loading-message" class="tile tile-muted text-center mt-3 w-100" >
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Loading...") %>
<div hidden data-selector="loading-message" class="col-lg-3" >
<div data-selector="chain-block">
<div class="tile tile-type-block n-p d-flex flex-column">
<a class="tile-title"><span class="tile-loader tile-label-loader"></span> </a>
<div class="tile-bottom-contents">
<div class="dark-block-loader"></div>
<div class="dark-block-loader"></div>
</div>
</div>
</div>
</div>
<div hidden data-selector="loading-message" class="col-lg-3" >
<div data-selector="chain-block">
<div class="tile tile-type-block n-p d-flex flex-column">
<a class="tile-title"><span class="tile-loader tile-label-loader"></span> </a>
<div class="tile-bottom-contents">
<div class="dark-block-loader"></div>
<div class="dark-block-loader"></div>
</div>
</div>
</div>
</div>
<div hidden data-selector="loading-message" class="col-lg-3" >
<div data-selector="chain-block">
<div class="tile tile-type-block n-p d-flex flex-column">
<a class="tile-title"><span class="tile-loader tile-label-loader"></span> </a>
<div class="tile-bottom-contents">
<div class="dark-block-loader"></div>
<div class="dark-block-loader"></div>
</div>
</div>
</div>
</div>
<div hidden data-selector="loading-message" class="col-lg-3" >
<div data-selector="chain-block">
<div class="tile tile-type-block n-p d-flex flex-column">
<a class="tile-title"><span class="tile-loader tile-label-loader"></span> </a>
<div class="tile-bottom-contents">
<div class="dark-block-loader"></div>
<div class="dark-block-loader"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -46,6 +46,54 @@
</div>
</div>
</div>
<div data-loading-message data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="tile-status-label ml-2 ml-md-0">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<span class="tile-loader tile-address-loader"></span>
<span class="tile-loader tile-address-loader"></span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0 tile-bottom">
<span class="mr-2 mr-md-0 order-1">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="mr-2 mr-md-0 order-2">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
</div>
</div>
<div data-loading-message data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">
<span class="tile-label">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="tile-status-label ml-2 ml-md-0">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<span class="tile-loader tile-address-loader"></span>
<span class="tile-loader tile-address-loader"></span>
</div>
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0 tile-bottom">
<span class="mr-2 mr-md-0 order-1">
<span class="tile-loader tile-label-loader"></span>
</span>
<span class="mr-2 mr-md-0 order-2">
<span class="tile-loader tile-label-loader"></span>
</span>
</div>
</div>
</div>
<div data-loading-message data-selector="loading-message" class="tile tile-type-loading">
<div class="row tile-body">
<div class="tile-transaction-type-block col-md-2 d-flex flex-row flex-md-column">

@ -71,7 +71,7 @@
<%= if total_supply?(@token) do %>
<div class="card-section col-md-12 col-lg-4 pl-0-md">
<div class="card card-background-1">
<div class="card-body">
<div class="card-body">
<h2 class="card-title balance-card-title"><%= gettext "Total Supply" %></h2>
<div class="text-right">
<h3 class="text-uppercase">

@ -23,10 +23,4 @@
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
</section>

@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view
alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain.{Address, Data, InternalTransaction}
alias Explorer.Chain.{Address, Data, InternalTransaction, SmartContract}
def render("scripts.html", %{conn: conn}) do
render_scripts(conn, "address_contract/code_highlighting.js")
@ -63,8 +63,11 @@ defmodule BlockScoutWeb.AddressContractView do
end)
end
def contract_lines_with_index(contract_source_code) do
contract_lines = String.split(contract_source_code, "\n")
def contract_lines_with_index(source_code, inserted_at \\ nil) do
contract_lines =
source_code
|> String.split("\n")
|> SmartContract.add_submitted_comment(inserted_at)
max_digits =
contract_lines

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.APIDocsView do
use BlockScoutWeb, :view
alias BlockScoutWeb.{Endpoint, LayoutView}
alias BlockScoutWeb.LayoutView
def action_tile_id(module, action) do
"#{module}-#{action}"
@ -34,30 +34,27 @@ defmodule BlockScoutWeb.APIDocsView do
end)
end
def blockscout_url do
defp blockscout_url do
url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
host = url_params[:host]
path = url_params[:path]
scheme = url_params[:scheme]
scheme = Keyword.get(url_params, :scheme, "http")
if host != "localhost" do
scheme <> "://" <> host <> path
"#{scheme}://#{host}#{path}"
else
Endpoint.url()
port = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:http][:port]
"#{scheme}://#{host}:#{to_string(port)}"
end
end
def api_url do
handle_slash("api")
blockscout_url()
|> Path.join("api")
end
def eth_rpc_api_url do
handle_slash("api/eth_rpc")
end
defp handle_slash(path) do
base_url = blockscout_url()
Path.join(base_url, path)
blockscout_url()
|> Path.join("api/eth_rpc")
end
end

@ -3,6 +3,15 @@ defmodule BlockScoutWeb.ChainView do
alias BlockScoutWeb.LayoutView
defp market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value})
when is_nil(available_supply) or is_nil(usd_value) do
Decimal.new(0)
end
defp market_cap(:standard, %{available_supply: available_supply, usd_value: usd_value}) do
Decimal.mult(available_supply, usd_value)
end
defp market_cap(:standard, exchange_rate) do
exchange_rate.market_cap_usd
end

@ -135,12 +135,12 @@ defmodule BlockScoutWeb.TransactionView do
def confirmations(%Transaction{block: block}, named_arguments) when is_list(named_arguments) do
case block do
nil ->
0
%Block{consensus: true} ->
{:ok, confirmations} = Chain.confirmations(block, named_arguments)
BlockScoutWeb.Cldr.Number.to_string!(confirmations, format: "#,###")
_ ->
0
end
end

@ -35,7 +35,7 @@ defmodule BlockScoutWeb.WeiHelpers do
"10,000 Gwei"
iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 21)}, :ether)
"10,000 POA"
"10,000 Ether"
# With formatting options
@ -43,7 +43,7 @@ defmodule BlockScoutWeb.WeiHelpers do
...> %Wei{value: Decimal.new(1000500000000000000)},
...> :ether
...> )
"1.0005 POA"
"1.0005 Ether"
iex> format_wei_value(
...> %Wei{value: Decimal.new(10)},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase
alias Explorer.Factory
alias Explorer.{Chain, Factory}
describe "listcontracts" do
setup do
@ -450,7 +450,9 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [
%{
"Address" => to_string(contract.address_hash),
"SourceCode" => contract.contract_source_code,
"SourceCode" =>
"/**\n* Submitted for verification at blockscout.com on #{contract.inserted_at}\n*/\n" <>
contract.contract_source_code,
"ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version,
@ -496,9 +498,13 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
|> get("/api", params)
|> json_response(200)
verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
expected_result = %{
"Address" => to_string(contract_address.hash),
"SourceCode" => contract_code_info.source_code,
"SourceCode" =>
"/**\n* Submitted for verification at blockscout.com on #{verified_contract.inserted_at}\n*/\n" <>
contract_code_info.source_code,
"ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version,
@ -563,8 +569,14 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
result = response["result"]
verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
assert result["Address"] == to_string(contract_address.hash)
assert result["SourceCode"] == contract_source_code
assert result["SourceCode"] ==
"/**\n* Submitted for verification at blockscout.com on #{verified_contract.inserted_at}\n*/\n" <>
contract_source_code
assert result["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == ""

@ -3,8 +3,8 @@ defmodule BlockScoutWeb.BlockControllerTest do
alias Explorer.Chain.Block
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
:ok
end

@ -9,8 +9,8 @@ defmodule BlockScoutWeb.ChainControllerTest do
alias Explorer.Counters.AddressesWithBalanceCounter
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
start_supervised!(AddressesWithBalanceCounter)
AddressesWithBalanceCounter.consolidate()

@ -73,7 +73,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
session
|> AddressPage.visit_page(address)
|> assert_text(AddressPage.balance(), "0.0000000000000005 POA")
|> assert_text(AddressPage.balance(), "0.0000000000000005 Ether")
end
describe "viewing contract creator" do

@ -10,8 +10,8 @@ defmodule BlockScoutWeb.ViewingChainTest do
alias Explorer.Counters.AddressesWithBalanceCounter
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Enum.map(401..404, &insert(:block, number: &1))

@ -8,13 +8,13 @@ defmodule BlockScoutWeb.AddressCoinBalanceViewTest do
test "format the wei value in ether" do
wei = Wei.from(Decimal.new(1_340_000_000), :gwei)
assert AddressCoinBalanceView.format(wei) == "1.34 POA"
assert AddressCoinBalanceView.format(wei) == "1.34 Ether"
end
test "format negative values" do
wei = Wei.from(Decimal.new(-1_340_000_000), :gwei)
assert AddressCoinBalanceView.format(wei) == "-1.34 POA"
assert AddressCoinBalanceView.format(wei) == "-1.34 Ether"
end
end
@ -50,13 +50,13 @@ defmodule BlockScoutWeb.AddressCoinBalanceViewTest do
test "format positive values" do
value = Decimal.new(1_340_000_000_000_000_000)
assert AddressCoinBalanceView.format_delta(value) == "1.34 POA"
assert AddressCoinBalanceView.format_delta(value) == "1.34 Ether"
end
test "format negative values" do
value = Decimal.new(-1_340_000_000_000_000_000)
assert AddressCoinBalanceView.format_delta(value) == "1.34 POA"
assert AddressCoinBalanceView.format_delta(value) == "1.34 Ether"
end
end
end

@ -38,22 +38,25 @@ defmodule BlockScoutWeb.AddressContractViewTest do
result = AddressContractView.contract_lines_with_index(code)
assert result == [
{"pragma solidity >=0.4.22 <0.6.0;", " 1"},
{"", " 2"},
{"struct Proposal {", " 3"},
{" uint voteCount;", " 4"},
{"}", " 5"},
{"", " 6"},
{"address chairperson;", " 7"},
{"mapping(address => Voter) voters;", " 8"},
{"Proposal[] proposals;", " 9"},
{"", "10"},
{"constructor(uint8 _numProposals) public {", "11"},
{" chairperson = msg.sender;", "12"},
{" voters[chairperson].weight = 1;", "13"},
{" proposals.length = _numProposals;", "14"},
{"}", "15"},
{"", "16"}
{"/**", " 1"},
{"* Submitted for verification at blockscout.com on ", " 2"},
{"*/", " 3"},
{"pragma solidity >=0.4.22 <0.6.0;", " 4"},
{"", " 5"},
{"struct Proposal {", " 6"},
{" uint voteCount;", " 7"},
{"}", " 8"},
{"", " 9"},
{"address chairperson;", "10"},
{"mapping(address => Voter) voters;", "11"},
{"Proposal[] proposals;", "12"},
{"", "13"},
{"constructor(uint8 _numProposals) public {", "14"},
{" chairperson = msg.sender;", "15"},
{" voters[chairperson].weight = 1;", "16"},
{" proposals.length = _numProposals;", "17"},
{"}", "18"},
{"", "19"}
]
end
@ -66,7 +69,17 @@ defmodule BlockScoutWeb.AddressContractViewTest do
test "returns a list of tuples and the first element is just a line from the original string" do
result = AddressContractView.contract_lines_with_index("a\nb\nc\nd\ne")
assert Enum.map(result, fn {line, _number} -> line end) == ["a", "b", "c", "d", "e"]
assert Enum.map(result, fn {line, _number} -> line end) == [
"/**",
"* Submitted for verification at blockscout.com on ",
"*/",
"a",
"b",
"c",
"d",
"e"
]
end
end
end

@ -3,32 +3,6 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
alias BlockScoutWeb.APIDocsView
describe "blockscout_url/0" do
setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
on_exit(fn -> Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, original) end)
:ok
end
test "returns url with scheme and host without port" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", port: 9999, path: "/"]
)
assert APIDocsView.blockscout_url() == "https://blockscout.com/"
end
test "returns url with scheme and host with path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", port: 9999, path: "/chain/dog"]
)
assert APIDocsView.blockscout_url() == "https://blockscout.com/chain/dog"
end
end
describe "api_url/1" do
setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
@ -53,6 +27,15 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
assert APIDocsView.api_url() == "https://blockscout.com/api"
end
test "localhost return with port" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "http", host: "localhost"],
http: [port: 9999]
)
assert APIDocsView.api_url() == "http://localhost:9999/api"
end
end
describe "eth_rpc_api_url/1" do
@ -79,5 +62,14 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
assert APIDocsView.eth_rpc_api_url() == "https://blockscout.com/api/eth_rpc"
end
test "localhost return with port" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "http", host: "localhost"],
http: [port: 9999]
)
assert APIDocsView.eth_rpc_api_url() == "http://localhost:9999/api/eth_rpc"
end
end
end

@ -91,7 +91,7 @@ defmodule BlockScoutWeb.BlockViewTest do
block = Repo.preload(block, :rewards)
assert BlockView.combined_rewards_value(block) == "3.000042 POA"
assert BlockView.combined_rewards_value(block) == "3.000042 Ether"
end
end
end

@ -136,7 +136,7 @@ defmodule BlockScoutWeb.TransactionViewTest do
gas_used: nil
)
expected_value = "Max of 0.009 POA"
expected_value = "Max of 0.009 Ether"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
end
@ -144,12 +144,9 @@ defmodule BlockScoutWeb.TransactionViewTest do
{:ok, gas_price} = Wei.cast(3_000_000_000)
transaction = build(:transaction, gas_price: gas_price, gas_used: Decimal.from_float(1_034_234.0))
expected_value = "0.003102702 POA"
expected_value = "0.003102702 Ether"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
end
test "with fee but no available exchange_rate" do
end
end
describe "formatted_status/1" do

@ -27,12 +27,18 @@ config :explorer, Explorer.Counters.AverageBlockTime,
enabled: true,
period: average_block_period
config :explorer, Explorer.ChainSpec.GenesisData, enabled: false, chain_spec_path: System.get_env("CHAIN_SPEC_PATH")
config :explorer, Explorer.ChainSpec.GenesisData,
enabled: true,
chain_spec_path: System.get_env("CHAIN_SPEC_PATH"),
emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"),
rewards_contract_address: System.get_env("REWARDS_CONTRACT_ADDRESS", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5")
config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true
config :explorer, Explorer.Chain.Cache.BlockNumber,
enabled: true,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap,
pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10")
config :explorer, Explorer.ExchangeRates.Source.CoinGecko, coin_id: System.get_env("COIN_GECKO_ID", "poa-network")
balances_update_interval =
if System.get_env("ADDRESS_WITH_BALANCES_UPDATE_INTERVAL") do
@ -124,6 +130,14 @@ market_history_cache_period =
config :explorer, Explorer.Market.MarketHistoryCache, period: market_history_cache_period
config :explorer, Explorer.Chain.Cache.Blocks,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.Transactions,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

@ -42,10 +42,11 @@ defmodule Explorer.Application do
Explorer.SmartContract.SolcDownloader,
{Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents},
{Admin.Recovery, [[], [name: Admin.Recovery]]},
{TransactionCount, [[], []]},
{BlockCount, []},
TransactionCount,
BlockCount,
Blocks,
con_cache_child_spec(NetVersion.cache_name()),
NetVersion,
BlockNumber,
con_cache_child_spec(MarketHistoryCache.cache_name()),
con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)),
Transactions
@ -55,11 +56,7 @@ defmodule Explorer.Application do
opts = [strategy: :one_for_one, name: Explorer.Supervisor]
res = Supervisor.start_link(children, opts)
BlockNumber.setup()
res
Supervisor.start_link(children, opts)
end
defp configurable_children do

@ -230,9 +230,8 @@ defmodule Explorer.Chain do
Reward.fetch_emission_rewards_tuples(address_hash, paging_options)
end)
address_hash
|> address_to_transactions_without_rewards(paging_options, options)
|> Enum.concat(Task.await(rewards_task, :timer.seconds(20)))
[rewards_task | address_to_transactions_tasks(address_hash, options)]
|> wait_for_address_transactions()
|> Enum.sort_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
@ -242,32 +241,83 @@ defmodule Explorer.Chain do
{-item.block_number, -item.index}
end
end)
|> Enum.dedup_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
{emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type}
transaction ->
transaction.hash
end
end)
|> Enum.take(paging_options.page_size)
else
address_to_transactions_without_rewards(address_hash, paging_options, options)
address_to_transactions_without_rewards(address_hash, options)
end
end
def address_to_transactions_without_rewards(address_hash, paging_options, options) do
def address_to_transactions_without_rewards(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hash
|> address_to_transactions_tasks(options)
|> wait_for_address_transactions()
|> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2)
|> Enum.dedup_by(& &1.hash)
|> Enum.take(paging_options.page_size)
end
defp address_to_transactions_tasks(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
transaction_hashes_from_token_transfers =
TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options)
base_query =
paging_options
|> fetch_transactions()
|> join_associations(necessity_by_association)
|> Transaction.preload_token_transfers(address_hash)
direction_tasks =
base_query
|> Transaction.matching_address_queries_list(direction, address_hash)
|> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end)
paging_options
|> fetch_transactions()
|> Transaction.where_transaction_matches(transaction_hashes_from_token_transfers, direction, address_hash)
|> join_associations(necessity_by_association)
|> Transaction.preload_token_transfers(address_hash)
|> Repo.all()
token_transfers_task =
Task.async(fn ->
transaction_hashes_from_token_transfers =
TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options)
final_query = where(base_query, [t], t.hash in ^transaction_hashes_from_token_transfers)
Repo.all(final_query)
end)
[token_transfers_task | direction_tasks]
end
defp wait_for_address_transactions(tasks) do
tasks
|> Task.yield_many(:timer.seconds(20))
|> Enum.flat_map(fn {_task, res} ->
case res do
{:ok, result} ->
result
{:exit, reason} ->
raise "Query fetching address transactions terminated: #{inspect(reason)}"
nil ->
raise "Query fetching address transactions timed out."
end
end)
end
@spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()]
def address_to_logs(address_hash, options \\ []) when is_list(options) do
paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50}
{block_number, transaction_index, log_index} = paging_options.key || {BlockNumber.max_number(), 0, 0}
{block_number, transaction_index, log_index} = paging_options.key || {BlockNumber.get_max(), 0, 0}
base_query =
from(log in Log,
@ -1176,7 +1226,7 @@ defmodule Explorer.Chain do
"""
@spec indexed_ratio() :: Decimal.t()
def indexed_ratio do
{min, max} = BlockNumber.min_and_max_numbers()
%{min: min, max: max} = BlockNumber.get_all()
case {min, max} do
{0, 0} ->
@ -1189,20 +1239,30 @@ defmodule Explorer.Chain do
end
end
@spec fetch_min_and_max_block_numbers() :: {non_neg_integer, non_neg_integer}
def fetch_min_and_max_block_numbers do
@spec fetch_min_block_number() :: non_neg_integer
def fetch_min_block_number do
query =
from(block in Block,
select: {min(block.number), max(block.number)},
where: block.consensus == true
select: block.number,
where: block.consensus == true,
order_by: [asc: block.number],
limit: 1
)
result = Repo.one!(query)
Repo.one(query) || 0
end
case result do
{nil, nil} -> {0, 0}
_ -> result
end
@spec fetch_max_block_number() :: non_neg_integer
def fetch_max_block_number do
query =
from(block in Block,
select: block.number,
where: block.consensus == true,
order_by: [desc: block.number],
limit: 1
)
Repo.one(query) || 0
end
@spec fetch_count_consensus_block() :: non_neg_integer
@ -2195,7 +2255,7 @@ defmodule Explorer.Chain do
"""
@spec transaction_estimated_count() :: non_neg_integer()
def transaction_estimated_count do
cached_value = TransactionCount.value()
cached_value = TransactionCount.get_count()
if is_nil(cached_value) do
%Postgrex.Result{rows: [[rows]]} =
@ -2214,7 +2274,7 @@ defmodule Explorer.Chain do
"""
@spec block_estimated_count() :: non_neg_integer()
def block_estimated_count do
cached_value = BlockCount.count()
cached_value = BlockCount.get_count()
if is_nil(cached_value) do
%Postgrex.Result{rows: [[count]]} = Repo.query!("SELECT reltuples FROM pg_class WHERE relname = 'blocks';")
@ -2532,8 +2592,26 @@ defmodule Explorer.Chain do
@spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
def address_hash_to_address_with_source_code(address_hash) do
case Repo.get(Address, address_hash) do
nil -> nil
address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
nil ->
nil
address ->
address_with_smart_contract = Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
if address_with_smart_contract.smart_contract do
formatted_code =
SmartContract.add_submitted_comment(
address_with_smart_contract.smart_contract.contract_source_code,
address_with_smart_contract.smart_contract.inserted_at
)
%{
address_with_smart_contract
| smart_contract: %{address_with_smart_contract.smart_contract | contract_source_code: formatted_code}
}
else
address_with_smart_contract
end
end
end
@ -2717,7 +2795,7 @@ defmodule Explorer.Chain do
end
defp supply_module do
Application.get_env(:explorer, :supply, Explorer.Chain.Supply.CoinMarketCap)
Application.get_env(:explorer, :supply, Explorer.Chain.Supply.ExchangeRate)
end
@doc """

@ -170,7 +170,7 @@ defmodule Explorer.Chain.Address do
end
def rsk_checksum(hash) do
chain_id = NetVersion.version()
chain_id = NetVersion.get_version()
string_hash =
hash

@ -42,8 +42,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
end
defp fetch_all_transactions(address_hash, paging_options, acc \\ []) do
transactions =
Chain.address_to_transactions_without_rewards(address_hash, paging_options, @necessity_by_association)
options = Keyword.put(@necessity_by_association, :paging_options, paging_options)
transactions = Chain.address_to_transactions_without_rewards(address_hash, options)
new_acc = transactions ++ acc

@ -5,143 +5,61 @@ defmodule Explorer.Chain.Cache.BlockCount do
require Logger
use GenServer
@default_cache_period :timer.minutes(10)
alias Explorer.Chain
# 10 minutes
@cache_period 1_000 * 60 * 10
@key "count"
@default_value nil
@name __MODULE__
def start_link(params) do
name = params[:name] || @name
params_with_name = Keyword.put(params, :name, name)
GenServer.start_link(__MODULE__, params_with_name, name: name)
end
def init(params) do
cache_period = period_from_env_var() || params[:cache_period] || @cache_period
current_value = params[:default_value] || @default_value
name = params[:name]
init_ets_table(name)
{:ok, {{cache_period, current_value, name}, nil}}
end
def count(process_name \\ __MODULE__) do
GenServer.call(process_name, :count)
end
def handle_call(:count, _, {{cache_period, default_value, name}, task}) do
{count, task} =
case cached_values(name) do
nil ->
{default_value, update_cache(task, name)}
{cached_value, timestamp} ->
task =
if current_time() - timestamp > cache_period do
update_cache(task, name)
end
{cached_value, task}
end
{:reply, count, {{cache_period, default_value, name}, task}}
end
def update_cache(nil, name) do
async_update_cache(name)
end
def update_cache(task, _) do
task
end
use Explorer.Chain.MapCache,
name: :block_count,
key: :count,
key: :async_task,
global_ttl: cache_period(),
ttl_check_interval: :timer.minutes(1),
callback: &async_task_on_deletion(&1)
def handle_cast({:update_cache, value}, {{cache_period, default_value, name}, _}) do
current_time = current_time()
tuple = {value, current_time}
table_name = table_name(name)
:ets.insert(table_name, {@key, tuple})
{:noreply, {{cache_period, default_value, name}, nil}}
end
def handle_info({:DOWN, _, _, _, _}, {{cache_period, default_value, name}, _}) do
{:noreply, {{cache_period, default_value, name}, nil}}
end
def handle_info(_, {{cache_period, default_value, name}, _}) do
{:noreply, {{cache_period, default_value, name}, nil}}
end
# sobelow_skip ["DOS"]
defp table_name(name) do
name
|> Atom.to_string()
|> Macro.underscore()
|> String.to_atom()
end
alias Explorer.Chain
def async_update_cache(name) do
Task.async(fn ->
try do
result = Chain.fetch_count_consensus_block()
defp handle_fallback(:count) do
# This will get the task PID if one exists and launch a new task if not
# See next `handle_fallback` definition
get_async_task()
GenServer.cast(name, {:update_cache, result})
rescue
e ->
Logger.debug([
"Coudn't update block count test #{inspect(e)}"
])
end
end)
{:return, nil}
end
defp init_ets_table(name) do
table_name = table_name(name)
if :ets.whereis(table_name) == :undefined do
:ets.new(table_name, [
:set,
:named_table,
:public,
write_concurrency: true
])
end
end
defp handle_fallback(:async_task) do
# If this gets called it means an async task was requested, but none exists
# so a new one needs to be launched
{:ok, task} =
Task.start(fn ->
try do
result = Chain.fetch_count_consensus_block()
set_count(result)
rescue
e ->
Logger.debug([
"Coudn't update block count test #{inspect(e)}"
])
end
defp cached_values(name) do
table_name = table_name(name)
set_async_task(nil)
end)
case :ets.lookup(table_name, @key) do
[{_, cached_values}] -> cached_values
_ -> nil
end
{:update, task}
end
defp current_time do
utc_now = DateTime.utc_now()
DateTime.to_unix(utc_now, :millisecond)
end
# By setting this as a `callback` an async task will be started each time the
# `count` expires (unless there is one already running)
defp async_task_on_deletion({:delete, _, :count}), do: get_async_task()
defp period_from_env_var do
case System.get_env("BLOCK_COUNT_CACHE_PERIOD") do
value when is_binary(value) ->
case Integer.parse(value) do
{integer, ""} -> integer * 1_000
_ -> nil
end
defp async_task_on_deletion(_data), do: nil
_ ->
nil
defp cache_period do
"BLOCK_COUNT_CACHE_PERIOD"
|> System.get_env("")
|> Integer.parse()
|> case do
{integer, ""} -> :timer.seconds(integer)
_ -> @default_cache_period
end
end
end

@ -3,89 +3,38 @@ defmodule Explorer.Chain.Cache.BlockNumber do
Cache for max and min block numbers.
"""
alias Explorer.Chain
@tab :block_number_cache
@key "min_max"
@type value :: non_neg_integer()
@spec setup() :: :ok
def setup do
if :ets.whereis(@tab) == :undefined do
:ets.new(@tab, [
:set,
:named_table,
:public,
write_concurrency: true
])
end
use Explorer.Chain.MapCache,
name: :block_number,
keys: [:min, :max],
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
update_cache()
:ok
end
alias Explorer.Chain
def max_number do
value(:max)
end
defp handle_update(_key, nil, value), do: {:ok, value}
def min_number do
value(:min)
end
defp handle_update(:min, old_value, new_value), do: {:ok, min(new_value, old_value)}
def min_and_max_numbers do
value(:all)
end
defp handle_update(:max, old_value, new_value), do: {:ok, max(new_value, old_value)}
defp value(type) do
{min, max} =
if Application.get_env(:explorer, __MODULE__)[:enabled] do
cached_values()
else
min_and_max_from_db()
end
defp handle_fallback(key) do
result = fetch_from_db(key)
case type do
:max -> max
:min -> min
:all -> {min, max}
if Application.get_env(:explorer, __MODULE__)[:enabled] do
{:update, result}
else
{:return, result}
end
end
@spec update(non_neg_integer()) :: boolean()
def update(number) do
{old_min, old_max} = cached_values()
cond do
number > old_max ->
tuple = {old_min, number}
:ets.insert(@tab, {@key, tuple})
number < old_min ->
tuple = {number, old_max}
:ets.insert(@tab, {@key, tuple})
true ->
false
defp fetch_from_db(key) do
case key do
:min -> Chain.fetch_min_block_number()
:max -> Chain.fetch_max_block_number()
end
end
defp update_cache do
{min, max} = min_and_max_from_db()
tuple = {min, max}
:ets.insert(@tab, {@key, tuple})
end
defp cached_values do
[{_, cached_values}] = :ets.lookup(@tab, @key)
cached_values
end
defp min_and_max_from_db do
Chain.fetch_min_and_max_block_numbers()
rescue
_e ->
{0, 0}
_e -> 0
end
end

@ -11,7 +11,9 @@ defmodule Explorer.Chain.Cache.Blocks do
ids_list_key: "block_numbers",
preload: :transactions,
preload: [miner: :names],
preload: :rewards
preload: :rewards,
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
@type element :: Block.t()

@ -3,42 +3,27 @@ defmodule Explorer.Chain.Cache.NetVersion do
Caches chain version.
"""
@cache_name :net_version
@key :version
@json_rpc_named_arguments Application.get_env(:explorer, :json_rpc_named_arguments)
@spec version() :: non_neg_integer() | {:error, any()}
def version do
cached_value = fetch_from_cache()
require Logger
if is_nil(cached_value) do
fetch_from_node()
else
cached_value
end
end
def cache_name do
@cache_name
end
defp fetch_from_cache do
ConCache.get(@cache_name, @key)
end
use Explorer.Chain.MapCache,
name: :net_version,
key: :version
defp cache_value(value) do
ConCache.put(@cache_name, @key, value)
end
defp fetch_from_node do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
case EthereumJSONRPC.fetch_net_version(json_rpc_named_arguments) do
defp handle_fallback(:version) do
case EthereumJSONRPC.fetch_net_version(@json_rpc_named_arguments) do
{:ok, value} ->
cache_value(value)
value
{:update, value}
other ->
other
{:error, reason} ->
Logger.debug([
"Coudn't fetch net_version, reason: #{inspect(reason)}"
])
{:return, nil}
end
end
defp handle_fallback(_key), do: {:return, nil}
end

@ -3,152 +3,64 @@ defmodule Explorer.Chain.Cache.TransactionCount do
Cache for estimated transaction count.
"""
require Logger
@default_cache_period :timer.hours(2)
use Explorer.Chain.MapCache,
name: :transaction_count,
key: :count,
key: :async_task,
global_ttl: cache_period(),
ttl_check_interval: :timer.minutes(10),
callback: &async_task_on_deletion(&1)
use GenServer
require Logger
alias Explorer.Chain.Transaction
alias Explorer.Repo
# 2 hours
@cache_period 1_000 * 60 * 60 * 2
@default_value nil
@key "count"
@name __MODULE__
def start_link([params, gen_server_options]) do
name = gen_server_options[:name] || @name
params_with_name = Keyword.put(params, :name, name)
GenServer.start_link(__MODULE__, params_with_name, name: name)
end
def init(params) do
cache_period = period_from_env_var() || params[:cache_period] || @cache_period
current_value = params[:default_value] || @default_value
name = params[:name]
init_ets_table(name)
schedule_cache_update()
{:ok, {{cache_period, current_value, name}, nil}}
end
def value(process_name \\ __MODULE__) do
GenServer.call(process_name, :value)
end
def handle_call(:value, _, {{cache_period, default_value, name}, task}) do
{value, task} =
case cached_values(name) do
nil ->
{default_value, update_cache(task, name)}
defp handle_fallback(:count) do
# This will get the task PID if one exists and launch a new task if not
# See next `handle_fallback` definition
get_async_task()
{cached_value, timestamp} ->
task =
if current_time() - timestamp > cache_period do
update_cache(task, name)
end
{cached_value, task}
end
{:reply, value, {{cache_period, default_value, name}, task}}
{:return, nil}
end
def update_cache(nil, name) do
async_update_cache(name)
end
def update_cache(task, _) do
task
end
def handle_cast({:update_cache, value}, {{cache_period, default_value, name}, _}) do
current_time = current_time()
tuple = {value, current_time}
table_name = table_name(name)
:ets.insert(table_name, {@key, tuple})
defp handle_fallback(:async_task) do
# If this gets called it means an async task was requested, but none exists
# so a new one needs to be launched
{:ok, task} =
Task.start(fn ->
try do
result = Repo.aggregate(Transaction, :count, :hash, timeout: :infinity)
{:noreply, {{cache_period, default_value, name}, nil}}
end
def handle_info({:DOWN, _, _, _, _}, {{cache_period, default_value, name}, _}) do
{:noreply, {{cache_period, default_value, name}, nil}}
end
def handle_info(_, {{cache_period, default_value, name}, _}) do
{:noreply, {{cache_period, default_value, name}, nil}}
end
# sobelow_skip ["DOS"]
defp table_name(name) do
name
|> Atom.to_string()
|> Macro.underscore()
|> String.to_atom()
end
def async_update_cache(name) do
Task.async(fn ->
try do
result = Repo.aggregate(Transaction, :count, :hash, timeout: :infinity)
GenServer.cast(name, {:update_cache, result})
rescue
e ->
Logger.debug([
"Coudn't update transaction count test #{inspect(e)}"
])
end
end)
end
defp init_ets_table(name) do
table_name = table_name(name)
if :ets.whereis(table_name) == :undefined do
:ets.new(table_name, [
:set,
:named_table,
:public,
write_concurrency: true
])
end
end
defp cached_values(name) do
table_name = table_name(name)
set_count(result)
rescue
e ->
Logger.debug([
"Coudn't update transaction count test #{inspect(e)}"
])
end
case :ets.lookup(table_name, @key) do
[{_, cached_values}] -> cached_values
_ -> nil
end
end
set_async_task(nil)
end)
defp schedule_cache_update do
Process.send_after(self(), :update_cache, 2_000)
{:update, task}
end
defp current_time do
utc_now = DateTime.utc_now()
# By setting this as a `callback` an async task will be started each time the
# `count` expires (unless there is one already running)
defp async_task_on_deletion({:delete, _, :count}), do: get_async_task()
DateTime.to_unix(utc_now, :millisecond)
end
defp period_from_env_var do
case System.get_env("TXS_COUNT_CACHE_PERIOD") do
value when is_binary(value) ->
case Integer.parse(value) do
{integer, ""} -> integer * 1_000
_ -> nil
end
defp async_task_on_deletion(_data), do: nil
_ ->
nil
defp cache_period do
"TXS_COUNT_CACHE_PERIOD"
|> System.get_env("")
|> Integer.parse()
|> case do
{integer, ""} -> :timer.seconds(integer)
_ -> @default_cache_period
end
end
end

@ -16,7 +16,9 @@ defmodule Explorer.Chain.Cache.Transactions do
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
]
],
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
@type element :: Transaction.t()

@ -0,0 +1,217 @@
defmodule Explorer.Chain.MapCache do
@moduledoc """
Behaviour for a map-like cache of elements.
A macro based on `ConCache` is provided as well, at its minimum it can be used as;
```
use Explorer.Chain.MapCache,
name: :name,
keys: [:fst, :snd]
```
Note: `keys` can also be set singularly with the option `key`, e.g.:
```
use Explorer.Chain.MapCache,
name: :cache,
key: :fst,
key: :snd
```
Additionally all of the options accepted by `ConCache.start_link/1` can be
provided as well. By default only `ttl_check_interval:` is set (to `false`).
## Named functions
Apart from the functions defined in the behaviour, the macro will also create
3 named function for each key, for instance for the key `:fst`:
- `get_fst`
- `set_fst`
- `update_fst`
These all work as their respective counterparts with the `t:key/0` parameter.
## Callbacks
Apart from the `callback` that can be set as part of the `ConCache` options,
two callbacks esist and can be overridden:
`c:handle_update/3` will be called whenever an update is issued. It will receive
the `t:key/0` that is going to be updated, the current `t:value/0` that is
stored for said key and the new `t:value/0` to evaluate.
This allows to select what value to keep and do additional processing.
By default this just stores the new `t:value/0`.
`c:handle_fallback/1` will be called whenever a get is performed and there is no
stored value for the given `t:key/0` (or when the value is `nil`).
It can return 2 different tuples:
- `{:update, value}` that will cause the value to be returned and the `t:key/0`
to be `c:update/2`d
- `{:return, value}` that will cause the value to be returned but not stored
This allows to define of a default value or perform some actions.
By default it will simply `{:return, nil}`
"""
@type key :: atom()
@type value :: term()
@doc """
An atom that identifies this cache
"""
@callback cache_name :: atom()
@doc """
List of `t:key/0`s that the cache contains
"""
@callback cache_keys :: [key()]
@doc """
Gets everything in a map
"""
@callback get_all :: map()
@doc """
Gets the stored `t:value/0` for a given `t:key/0`
"""
@callback get(atom()) :: value()
@doc """
Stores the same `t:value/0` for every `t:key/0`
"""
@callback set_all(value()) :: :ok
@doc """
Stores the given `t:value/0` for the given `t:key/0`
"""
@callback set(key(), value()) :: :ok
@doc """
Updates every `t:key/0` with the given `t:value/0`
"""
@callback update_all(value()) :: :ok
@doc """
Updates the given `t:key/0` (or every `t:key/0` in a list) using the given `t:value/0`
"""
@callback update(key() | [key()], value()) :: :ok
@doc """
Gets called during an update for the given `t:key/0`
"""
@callback handle_update(key(), value(), value()) :: {:ok, value()} | {:error, term()}
@doc """
Gets called when a `c:get/1` finds no `t:value/0`
"""
@callback handle_fallback(key()) :: {:update, value()} | {:return, value()}
# credo:disable-for-next-line /Complexity/
defmacro __using__(opts) when is_list(opts) do
# name is necessary
name = Keyword.fetch!(opts, :name)
keys = Keyword.get(opts, :keys) || Keyword.get_values(opts, :key)
concache_params =
opts
|> Keyword.drop([:keys, :key])
|> Keyword.put_new(:ttl_check_interval, false)
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
alias Explorer.Chain.MapCache
@behaviour MapCache
@dialyzer {:nowarn_function, handle_fallback: 1}
@impl MapCache
def cache_name, do: unquote(name)
@impl MapCache
def cache_keys, do: unquote(keys)
@impl MapCache
def get_all do
Map.new(cache_keys(), fn key -> {key, get(key)} end)
end
@impl MapCache
def get(key) do
case ConCache.get(cache_name(), key) do
nil ->
case handle_fallback(key) do
{:update, new_value} ->
update(key, new_value)
new_value
{:return, new_value} ->
new_value
end
value ->
value
end
end
@impl MapCache
def set_all(value) do
Enum.each(cache_keys(), &set(&1, value))
end
@impl MapCache
def set(key, value) do
ConCache.put(cache_name(), key, value)
end
@impl MapCache
def update_all(value), do: update(cache_keys(), value)
@impl MapCache
def update(keys, value) when is_list(keys) do
Enum.each(keys, &update(&1, value))
end
@impl MapCache
def update(key, value) do
ConCache.update(cache_name(), key, fn old_val -> handle_update(key, old_val, value) end)
end
### Autogenerated named functions
unquote(Enum.map(keys, &named_functions(&1)))
### Overridable callback functions
@impl MapCache
def handle_update(_key, _old_value, new_value), do: {:ok, new_value}
@impl MapCache
def handle_fallback(_key), do: {:return, nil}
defoverridable handle_update: 3, handle_fallback: 1
### Supervisor's child specification
@doc """
The child specification for a Supervisor. Note that all the `params`
provided to this function will override the ones set by using the macro
"""
def child_spec(params \\ []) do
params = Keyword.merge(unquote(concache_params), params)
Supervisor.child_spec({ConCache, params}, id: child_id())
end
def child_id, do: {ConCache, cache_name()}
end
end
# sobelow_skip ["DOS"]
defp named_functions(key) do
quote do
# sobelow_skip ["DOS"]
def unquote(:"get_#{key}")(), do: get(unquote(key))
# sobelow_skip ["DOS"]
def unquote(:"set_#{key}")(value), do: set(unquote(key), value)
# sobelow_skip ["DOS"]
def unquote(:"update_#{key}")(value), do: update(unquote(key), value)
end
end
end

@ -26,7 +26,10 @@ defmodule Explorer.Chain.OrderedCache do
preload: [transaction: :hash]
```
Additionally all of the options accepted by `ConCache.start_link/1` can be
provided as well. By default only `ttl_check_interval:` is set (to `false`).
provided as well. Unless specified, only these values have defaults:
- `:ttl_check_interval` is set (to `false`).
- `:callback` is only set if `:ttl_check_interval` is not `false` to call the
`remove_deleted_from_index` function, that removes expired values from the index.
It's also possible, and advised, to override the implementation of the `c:prevails?/2`
and `c:element_to_id/1` callbacks.
@ -131,10 +134,7 @@ defmodule Explorer.Chain.OrderedCache do
max_size = Keyword.get(opts, :max_size, 100)
preloads = Keyword.get(opts, :preloads) || Keyword.get_values(opts, :preload)
concache_params =
opts
|> Keyword.drop([:ids_list_key, :max_size, :preloads, :preload])
|> Keyword.put_new(:ttl_check_interval, false)
concache_params = Keyword.drop(opts, [:ids_list_key, :max_size, :preloads, :preload])
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
@ -206,6 +206,19 @@ defmodule Explorer.Chain.OrderedCache do
### Updating function
def remove_deleted_from_index({:delete, _cache_pid, id}) do
# simply check with `ConCache.get` because it is faster
if Enum.member?(ids_list(), id) do
ConCache.update(cache_name(), ids_list_key(), fn ids ->
updated_list = List.delete(ids || [], id)
# ids_list is set to never expire
{:ok, %ConCache.Item{value: updated_list, ttl: :infinity}}
end)
end
end
def remove_deleted_from_index(_), do: nil
@impl OrderedCache
def update(elements) when is_nil(elements), do: :ok
@ -217,7 +230,8 @@ defmodule Explorer.Chain.OrderedCache do
|> Enum.sort(&prevails?(&1, &2))
|> merge_and_update(ids || [], max_size())
{:ok, updated_list}
# ids_list is set to never expire
{:ok, %ConCache.Item{value: updated_list, ttl: :infinity}}
end)
end
@ -308,7 +322,21 @@ defmodule Explorer.Chain.OrderedCache do
provided to this function will override the ones set by using the macro
"""
def child_spec(params) do
params = Keyword.merge(unquote(concache_params), params)
# params specified in `use`
merged_params =
unquote(concache_params)
# params specified in `child_spec`
|> Keyword.merge(params)
# `:ttl_check_interval` needs to be specified, defaults to `false`
|> Keyword.put_new(:ttl_check_interval, false)
# if `:ttl_check_interval` is not `false` the expired values need to be
# removed from the cache's index
params =
case merged_params[:ttl_check_interval] do
false -> merged_params
_ -> Keyword.put_new(merged_params, :callback, &remove_deleted_from_index/1)
end
Supervisor.child_spec({ConCache, params}, id: child_id())
end

@ -271,6 +271,45 @@ defmodule Explorer.Chain.SmartContract do
|> add_error(:contract_source_code, error_message(error))
end
def add_submitted_comment(code, inserted_at) when is_binary(code) do
code
|> String.split("\n")
|> add_submitted_comment(inserted_at)
|> Enum.join("\n")
end
def add_submitted_comment(contract_lines, inserted_at) when is_list(contract_lines) do
etherscan_index =
Enum.find_index(contract_lines, fn line ->
String.contains?(line, "Submitted for verification at Etherscan.io")
end)
blockscout_index =
Enum.find_index(contract_lines, fn line ->
String.contains?(line, "Submitted for verification at blockscout.com")
end)
cond do
etherscan_index && blockscout_index ->
List.replace_at(contract_lines, etherscan_index, "*")
etherscan_index && !blockscout_index ->
List.replace_at(
contract_lines,
etherscan_index,
"* Submitted for verification at blockscout.com on #{inserted_at}"
)
!etherscan_index && !blockscout_index ->
header = ["/**", "* Submitted for verification at blockscout.com on #{inserted_at}", "*/"]
header ++ contract_lines
true ->
contract_lines
end
end
defp upsert_contract_methods(%Ecto.Changeset{changes: %{abi: abi}} = changeset) do
ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash))

@ -1,6 +1,6 @@
defmodule Explorer.Chain.Supply.CoinMarketCap do
defmodule Explorer.Chain.Supply.ExchangeRate do
@moduledoc """
Defines the supply API for calculating supply for coins from coinmarketcap.
Defines the supply API for calculating supply for coins from exchange_rate..
"""
use Explorer.Chain.Supply

@ -102,7 +102,7 @@ defmodule Explorer.Chain.Supply.RSK do
def cache_name, do: @cache_name
defp fetch_circulating_value do
max_number = BlockNumber.max_number()
max_number = BlockNumber.get_max()
params = [
%{block_quantity: integer_to_quantity(max_number), hash_data: "0x0000000000000000000000000000000001000006"}

@ -484,40 +484,26 @@ defmodule Explorer.Chain.Transaction do
end
@doc """
Modifies a query to filter for transactions whose hash is in a list or that are
linked to the given address_hash through a direction.
Be careful to not pass a large list, because this will lead to performance
problems.
Produces a list of queries starting from the given one and adding filters for
transactions that are linked to the given address_hash through a direction.
"""
def where_transaction_matches(query, transaction_hashes, :from, address_hash) do
where(
query,
[t],
t.hash in ^transaction_hashes or
t.from_address_hash == ^address_hash
)
def matching_address_queries_list(query, :from, address_hash) do
[where(query, [t], t.from_address_hash == ^address_hash)]
end
def where_transaction_matches(query, transaction_hashes, :to, address_hash) do
where(
query,
[t],
t.hash in ^transaction_hashes or
t.to_address_hash == ^address_hash or
t.created_contract_address_hash == ^address_hash
)
def matching_address_queries_list(query, :to, address_hash) do
[
where(query, [t], t.to_address_hash == ^address_hash),
where(query, [t], t.created_contract_address_hash == ^address_hash)
]
end
def where_transaction_matches(query, transaction_hashes, _direction, address_hash) do
where(
query,
[t],
t.hash in ^transaction_hashes or
t.from_address_hash == ^address_hash or
t.to_address_hash == ^address_hash or
t.created_contract_address_hash == ^address_hash
)
def matching_address_queries_list(query, _direction, address_hash) do
[
where(query, [t], t.from_address_hash == ^address_hash),
where(query, [t], t.to_address_hash == ^address_hash),
where(query, [t], t.created_contract_address_hash == ^address_hash)
]
end
@collated_fields ~w(block_number cumulative_gas_used gas_used index)a

@ -3,18 +3,23 @@ defmodule Explorer.ChainSpec.Parity.Importer do
Imports data from parity chain spec.
"""
require Logger
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Hash.Address, as: AddressHash
alias Explorer.Chain.Wei
alias Explorer.ChainSpec.GenesisData
alias Explorer.ChainSpec.POA.Importer, as: PoaEmissionImporter
@max_block_number :infinity
def import_emission_rewards(chain_spec) do
rewards = emission_rewards(chain_spec)
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
if Application.get_env(:explorer, GenesisData)[:emission_format] == "POA" do
PoaEmissionImporter.import_emission_rewards()
else
import_rewards_from_chain_spec(chain_spec)
end
end
def import_genesis_coin_balances(chain_spec) do
@ -38,18 +43,37 @@ defmodule Explorer.ChainSpec.Parity.Importer do
Chain.import(params)
end
defp import_rewards_from_chain_spec(chain_spec) do
rewards = emission_rewards(chain_spec)
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
end
def genesis_coin_balances(chain_spec) do
accounts = chain_spec["accounts"]
parse_accounts(accounts)
if accounts do
parse_accounts(accounts)
else
Logger.warn(fn -> "No accounts are defined in chain spec" end)
[]
end
end
def emission_rewards(chain_spec) do
rewards = chain_spec["engine"]["Ethash"]["params"]["blockReward"]
rewards
|> parse_hex_numbers()
|> format_ranges()
if rewards do
rewards
|> parse_hex_numbers()
|> format_ranges()
else
Logger.warn(fn -> "No rewards are defined in chain spec" end)
[]
end
end
defp parse_accounts(accounts) do
@ -59,7 +83,7 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end)
|> Stream.map(fn {address, %{"balance" => value}} ->
{:ok, address_hash} = AddressHash.cast(address)
balance = parse_hex_number(value)
balance = parse_number(value)
%{address_hash: address_hash, value: balance}
end)
@ -92,16 +116,22 @@ defmodule Explorer.ChainSpec.Parity.Importer do
defp parse_hex_numbers(rewards) do
Enum.map(rewards, fn {hex_block_number, hex_reward} ->
block_number = parse_hex_number(hex_block_number)
{:ok, reward} = hex_reward |> parse_hex_number() |> Wei.cast()
block_number = parse_number(hex_block_number)
{:ok, reward} = hex_reward |> parse_number() |> Wei.cast()
{block_number, reward}
end)
end
defp parse_hex_number("0x" <> hex_number) do
defp parse_number("0x" <> hex_number) do
{number, ""} = Integer.parse(hex_number, 16)
number
end
defp parse_number(string_number) do
{number, ""} = Integer.parse(string_number, 10)
number
end
end

@ -0,0 +1,89 @@
defmodule Explorer.ChainSpec.POA.Importer do
@moduledoc """
Imports emission reward range for POA chain.
"""
require Logger
alias Explorer.Chain.Wei
alias Explorer.Repo
alias Explorer.SmartContract.Reader
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.ChainSpec.GenesisData
@block_reward_amount_abi %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "blockRewardAmount",
"inputs" => [],
"constant" => true
}
@block_reward_amount_params %{"blockRewardAmount" => []}
@emission_funds_amount_abi %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "uint256", "name" => ""}],
"name" => "emissionFundsAmount",
"inputs" => [],
"constant" => true
}
@emission_funds_amount_params %{"emissionFundsAmount" => []}
@emission_funds_block_start 5_098_087
def import_emission_rewards do
if is_nil(rewards_contract_address()) do
Logger.warn(fn -> "No rewards contract address is defined" end)
else
block_reward = block_reward_amount()
emission_funds = emission_funds_amount()
rewards = [
%{
block_range: %Range{from: 0, to: @emission_funds_block_start},
reward: %Wei{value: block_reward}
},
%{
block_range: %Range{from: @emission_funds_block_start + 1, to: :infinity},
reward: %Wei{value: Decimal.add(block_reward, emission_funds)}
}
]
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
end
end
def block_reward_amount do
call_contract(rewards_contract_address(), @block_reward_amount_abi, @block_reward_amount_params)
end
def emission_funds_amount do
call_contract(rewards_contract_address(), @emission_funds_amount_abi, @emission_funds_amount_params)
end
defp rewards_contract_address do
Application.get_env(:explorer, GenesisData)[:rewards_contract_address]
end
defp call_contract(address, abi, params) do
abi = [abi]
method_name =
params
|> Enum.map(fn {key, _value} -> key end)
|> List.first()
Reader.query_contract(address, abi, params)
value =
case Reader.query_contract(address, abi, params) do
%{^method_name => {:ok, [result]}} -> result
_ -> 0
end
Decimal.new(value)
end
end

@ -2,8 +2,6 @@ defmodule Explorer.ExchangeRates.Source do
@moduledoc """
Behaviour for fetching exchange rates from external sources.
"""
alias Explorer.ExchangeRates.Source.CoinMarketCap
alias Explorer.ExchangeRates.Token
alias HTTPoison.{Error, Response}
@ -12,34 +10,18 @@ defmodule Explorer.ExchangeRates.Source do
"""
@spec fetch_exchange_rates(module) :: {:ok, [Token.t()]} | {:error, any}
def fetch_exchange_rates(source \\ exchange_rates_source()) do
if(source == CoinMarketCap) do
fetch_exchange_rates_from_paginable_source(source)
else
fetch_exchange_rates_request(source)
end
end
defp fetch_exchange_rates_from_paginable_source(source, page \\ 1) do
case HTTPoison.get(source.source_url(page), headers()) do
{:ok, %Response{body: body, status_code: 200}} ->
cond do
body =~ Explorer.coin() -> {:ok, source.format_data(body)}
page == source.max_page_number -> {:error, "exchange rates not found for this network"}
true -> fetch_exchange_rates_from_paginable_source(source, page + 1)
end
{:ok, %Response{body: body, status_code: status_code}} when status_code in 400..502 ->
{:error, decode_json(body)["error"]}
{:error, %Error{reason: reason}} ->
{:error, reason}
end
fetch_exchange_rates_request(source)
end
defp fetch_exchange_rates_request(source) do
case HTTPoison.get(source.source_url(), headers()) do
{:ok, %Response{body: body, status_code: 200}} ->
{:ok, source.format_data(body)}
result =
body
|> decode_json()
|> source.format_data()
{:ok, result}
{:ok, %Response{body: body, status_code: status_code}} when status_code in 400..499 ->
{:error, decode_json(body)["error"]}
@ -83,7 +65,7 @@ defmodule Explorer.ExchangeRates.Source do
@spec exchange_rates_source() :: module()
defp exchange_rates_source do
config(:source) || Explorer.ExchangeRates.Source.CoinMarketCap
config(:source) || Explorer.ExchangeRates.Source.CoinGecko
end
@spec config(atom()) :: term

@ -11,43 +11,50 @@ defmodule Explorer.ExchangeRates.Source.CoinGecko do
@behaviour Source
@impl Source
def format_data(data) do
def format_data(%{"market_data" => _} = json_data) do
{:ok, price} = get_btc_price()
btc_price = to_decimal(price)
for item <- decode_json(data),
not is_nil(item["total_supply"]) and not is_nil(item["current_price"]) do
{:ok, last_updated, 0} = DateTime.from_iso8601(item["last_updated"])
market_data = json_data["market_data"]
{:ok, last_updated, 0} = DateTime.from_iso8601(market_data["last_updated"])
current_price = to_decimal(item["current_price"])
current_price = to_decimal(market_data["current_price"]["usd"])
id = item["id"]
btc_value = if id != "btc", do: Decimal.div(current_price, btc_price), else: 1
id = json_data["id"]
btc_value = if id != "btc", do: Decimal.div(current_price, btc_price), else: 1
[
%Token{
available_supply: to_decimal(item["total_supply"]),
total_supply: to_decimal(item["total_supply"]),
available_supply: to_decimal(market_data["circulating_supply"]),
total_supply: to_decimal(market_data["total_supply"]),
btc_value: btc_value,
id: id,
id: json_data["id"],
last_updated: last_updated,
market_cap_usd: to_decimal(item["market_cap"]),
name: item["name"],
symbol: item["symbol"],
market_cap_usd: to_decimal(market_data["market_cap"]["usd"]),
name: json_data["name"],
symbol: String.upcase(json_data["symbol"]),
usd_value: current_price,
volume_24h_usd: to_decimal(item["total_volume"])
volume_24h_usd: to_decimal(market_data["total_volume"]["usd"])
}
end
]
end
@impl Source
def source_url(currency \\ "usd") do
"#{base_url()}/coins/markets?vs_currency=#{currency}"
def format_data(_), do: []
@impl Source
def source_url do
"#{base_url()}/coins/#{coin_id()}"
end
defp base_url do
config(:base_url) || "https://api.coingecko.com/api/v3"
end
defp coin_id do
Application.get_env(:explorer, __MODULE__)[:coin_id]
end
defp get_btc_price(currency \\ "usd") do
url = "#{base_url()}/exchange_rates"

@ -1,52 +0,0 @@
defmodule Explorer.ExchangeRates.Source.CoinMarketCap do
@moduledoc """
Adapter for fetching exchange rates from https://coinmarketcap.com.
"""
alias Explorer.ExchangeRates.{Source, Token}
import Source, only: [decode_json: 1, to_decimal: 1]
@behaviour Source
@impl Source
def format_data(data) do
for item <- decode_json(data), not is_nil(item["last_updated"]) do
{last_updated_as_unix, _} = Integer.parse(item["last_updated"])
last_updated = DateTime.from_unix!(last_updated_as_unix)
%Token{
available_supply: to_decimal(item["available_supply"]),
total_supply: to_decimal(item["total_supply"]),
btc_value: to_decimal(item["price_btc"]),
id: item["id"],
last_updated: last_updated,
market_cap_usd: to_decimal(item["market_cap_usd"]),
name: item["name"],
symbol: item["symbol"],
usd_value: to_decimal(item["price_usd"]),
volume_24h_usd: to_decimal(item["24h_volume_usd"])
}
end
end
@impl Source
def source_url do
source_url(1)
end
def source_url(page) do
"#{base_url()}/v1/ticker/?start=#{page - 1}00"
end
def max_page_number, do: config(:pages)
defp base_url do
config(:base_url) || "https://api.coinmarketcap.com"
end
@spec config(atom()) :: term
defp config(key) do
Application.get_env(:explorer, __MODULE__, [])[key]
end
end

@ -30,7 +30,7 @@ defmodule Explorer.ExchangeRates.Source.TokenBridge do
btc_value: original_token.btc_value,
id: original_token.id,
last_updated: original_token.last_updated,
market_cap_usd: Decimal.mult(to_decimal(Chain.circulating_supply()), original_token.usd_value),
market_cap_usd: market_cap_usd(Chain.circulating_supply(), original_token),
name: original_token.name,
symbol: original_token.symbol,
usd_value: original_token.usd_value,
@ -38,6 +38,14 @@ defmodule Explorer.ExchangeRates.Source.TokenBridge do
}
end
defp market_cap_usd(nil, _original_token), do: Decimal.new(0)
defp market_cap_usd(supply, original_token) do
supply
|> to_decimal()
|> Decimal.mult(original_token.usd_value)
end
@impl Source
def source_url do
secondary_source().source_url()
@ -45,7 +53,7 @@ defmodule Explorer.ExchangeRates.Source.TokenBridge do
@spec secondary_source() :: module()
defp secondary_source do
config(:secondary_source) || Explorer.ExchangeRates.Source.CoinMarketCap
config(:secondary_source) || Explorer.ExchangeRates.Source.CoinGecko
end
@spec config(atom()) :: term

@ -3,53 +3,53 @@ defmodule Explorer.Chain.Cache.BlockCountTest do
alias Explorer.Chain.Cache.BlockCount
test "returns default transaction count" do
BlockCount.start_link(name: BlockTestCache)
setup do
Supervisor.terminate_child(Explorer.Supervisor, BlockCount.child_id())
Supervisor.restart_child(Explorer.Supervisor, BlockCount.child_id())
:ok
end
result = BlockCount.count(BlockTestCache)
test "returns default transaction count" do
result = BlockCount.get_count()
assert is_nil(result)
end
test "updates cache if initial value is zero" do
BlockCount.start_link(name: BlockTestCache)
insert(:block, consensus: true)
insert(:block, consensus: true)
insert(:block, consensus: false)
_result = BlockCount.count(BlockTestCache)
_result = BlockCount.get_count()
Process.sleep(1000)
updated_value = BlockCount.count(BlockTestCache)
updated_value = BlockCount.get_count()
assert updated_value == 2
end
test "does not update cache if cache period did not pass" do
BlockCount.start_link(name: BlockTestCache)
insert(:block, consensus: true)
insert(:block, consensus: true)
insert(:block, consensus: false)
_result = BlockCount.count(BlockTestCache)
_result = BlockCount.get_count()
Process.sleep(1000)
updated_value = BlockCount.count(BlockTestCache)
updated_value = BlockCount.get_count()
assert updated_value == 2
insert(:block, consensus: true)
insert(:block, consensus: true)
_updated_value = BlockCount.count(BlockTestCache)
_updated_value = BlockCount.get_count()
Process.sleep(1000)
updated_value = BlockCount.count(BlockTestCache)
updated_value = BlockCount.get_count()
assert updated_value == 2
end

@ -11,49 +11,63 @@ defmodule Explorer.Chain.Cache.BlockNumberTest do
end)
end
describe "max_number/1" do
describe "get_max/0" do
test "returns max number" do
insert(:block, number: 5)
BlockNumber.setup()
assert BlockNumber.max_number() == 5
assert BlockNumber.get_max() == 5
end
end
describe "min_number/1" do
test "returns max number" do
describe "get_min/0" do
test "returns min number" do
insert(:block, number: 2)
BlockNumber.setup()
assert BlockNumber.get_min() == 2
end
end
assert BlockNumber.max_number() == 2
describe "get_all/0" do
test "returns min and max number" do
insert(:block, number: 6)
assert BlockNumber.get_all() == %{min: 6, max: 6}
end
end
describe "update/1" do
describe "update_all/1" do
test "updates max number" do
insert(:block, number: 2)
BlockNumber.setup()
assert BlockNumber.max_number() == 2
assert BlockNumber.get_max() == 2
assert BlockNumber.update(3)
assert BlockNumber.update_all(3)
assert BlockNumber.max_number() == 3
assert BlockNumber.get_max() == 3
end
test "updates min number" do
insert(:block, number: 2)
BlockNumber.setup()
assert BlockNumber.get_min() == 2
assert BlockNumber.update_all(1)
assert BlockNumber.get_min() == 1
end
test "updates min and number" do
insert(:block, number: 2)
assert BlockNumber.get_all() == %{min: 2, max: 2}
assert BlockNumber.update_all(1)
assert BlockNumber.min_number() == 2
assert BlockNumber.get_all() == %{min: 1, max: 2}
assert BlockNumber.update(1)
assert BlockNumber.update_all(6)
assert BlockNumber.min_number() == 1
assert BlockNumber.get_all() == %{min: 1, max: 6}
end
end
end

@ -5,8 +5,8 @@ defmodule Explorer.Chain.Cache.BlocksTest do
alias Explorer.Repo
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.terminate_child(Explorer.Supervisor, Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Blocks.child_id())
:ok
end

@ -3,51 +3,51 @@ defmodule Explorer.Chain.Cache.TransactionCountTest do
alias Explorer.Chain.Cache.TransactionCount
test "returns default transaction count" do
TransactionCount.start_link([[], [name: TestCache]])
setup do
Supervisor.terminate_child(Explorer.Supervisor, TransactionCount.child_id())
Supervisor.restart_child(Explorer.Supervisor, TransactionCount.child_id())
:ok
end
result = TransactionCount.value(TestCache)
test "returns default transaction count" do
result = TransactionCount.get_count()
assert is_nil(result)
end
test "updates cache if initial value is zero" do
TransactionCount.start_link([[], [name: TestCache]])
insert(:transaction)
insert(:transaction)
_result = TransactionCount.value(TestCache)
_result = TransactionCount.get_count()
Process.sleep(1000)
updated_value = TransactionCount.value(TestCache)
updated_value = TransactionCount.get_count()
assert updated_value == 2
end
test "does not update cache if cache period did not pass" do
TransactionCount.start_link([[], [name: TestCache]])
insert(:transaction)
insert(:transaction)
_result = TransactionCount.value(TestCache)
_result = TransactionCount.get_count()
Process.sleep(1000)
updated_value = TransactionCount.value(TestCache)
updated_value = TransactionCount.get_count()
assert updated_value == 2
insert(:transaction)
insert(:transaction)
_updated_value = TransactionCount.value(TestCache)
_updated_value = TransactionCount.get_count()
Process.sleep(1000)
updated_value = TransactionCount.value(TestCache)
updated_value = TransactionCount.get_count()
assert updated_value == 2
end

@ -1121,23 +1121,31 @@ defmodule Explorer.ChainTest do
end
end
describe "fetch_min_and_max_block_numbers/0" do
test "fetches min and max block numbers" do
describe "fetch_min_block_number/0" do
test "fetches min block numbers" do
for index <- 5..9 do
insert(:block, number: index)
end
assert {5, 9} = Chain.fetch_min_and_max_block_numbers()
assert 5 = Chain.fetch_min_block_number()
end
test "fetches min and max when there are no blocks" do
assert {0, 0} = Chain.fetch_min_and_max_block_numbers()
test "fetches min when there are no blocks" do
assert 0 = Chain.fetch_min_block_number()
end
end
test "fetches min and max where there is only one block" do
insert(:block, number: 1)
describe "fetch_max_block_number/0" do
test "fetches max block numbers" do
for index <- 5..9 do
insert(:block, number: index)
end
assert 9 = Chain.fetch_max_block_number()
end
assert {1, 1} = Chain.fetch_min_and_max_block_numbers()
test "fetches max when there are no blocks" do
assert 0 = Chain.fetch_max_block_number()
end
end

@ -18,34 +18,6 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do
}
"""
@json_mkt_data """
[
{
"id": "poa-network",
"symbol": "poa",
"name": "POA Network",
"image": "https://assets.coingecko.com/coins/images/3157/large/poa.jpg?1520829019",
"current_price": 0.114782883773693,
"market_cap": 25248999.6735956,
"market_cap_rank": 185,
"total_volume": 2344442.13578437,
"high_24h": 0.115215129840519,
"low_24h": 0.101039753612939,
"price_change_24h": 0.0135970966607094,
"price_change_percentage_24h": 13.437753511298,
"market_cap_change_24h": 3058195.58191147,
"market_cap_change_percentage_24h": 13.7813644304017,
"circulating_supply": "219935174.0",
"total_supply": 252193195,
"ath": 0.935923393359191,
"ath_change_percentage": -87.731057963078,
"ath_date": "2018-05-10T09:45:31.809Z",
"roi": null,
"last_updated": "2018-10-23T01:25:31.764Z"
}
]
"""
describe "format_data/1" do
setup do
bypass = Bypass.open()
@ -59,31 +31,30 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do
Conn.resp(conn, 200, @json_btc_price)
end)
{:ok, expected_date, 0} = "2018-10-23T01:25:31.764Z" |> DateTime.from_iso8601()
json_data =
"#{File.cwd!()}/test/support/fixture/exchange_rates/coin_gecko.json"
|> File.read!()
|> Jason.decode!()
expected = [
%Token{
available_supply: Decimal.new("252193195"),
total_supply: Decimal.new("252193195"),
btc_value: Decimal.new("0.00001753101509231471092879666458"),
available_supply: Decimal.new("220167621.0"),
total_supply: Decimal.new("252193195.0"),
btc_value: Decimal.new("0.000002055310963802830367634997491"),
id: "poa-network",
last_updated: expected_date,
market_cap_usd: Decimal.new("25248999.6735956"),
last_updated: ~U[2019-08-21 08:36:49.371Z],
market_cap_usd: Decimal.new("2962791"),
name: "POA Network",
symbol: "poa",
usd_value: Decimal.new("0.114782883773693"),
volume_24h_usd: Decimal.new("2344442.13578437")
symbol: "POA",
usd_value: Decimal.new("0.01345698"),
volume_24h_usd: Decimal.new("119946")
}
]
assert expected == CoinGecko.format_data(@json_mkt_data)
assert expected == CoinGecko.format_data(json_data)
end
test "returns nothing when given bad data", %{bypass: bypass} do
Bypass.expect(bypass, "GET", "/exchange_rates", fn conn ->
Conn.resp(conn, 200, @json_btc_price)
end)
test "returns nothing when given bad data" do
bad_data = """
[{"id": "poa-network"}]
"""

@ -1,59 +0,0 @@
defmodule Explorer.ExchangeRates.Source.CoinMarketCapTest do
use ExUnit.Case
alias Explorer.ExchangeRates.Token
alias Explorer.ExchangeRates.Source.CoinMarketCap
@json """
[
{
"id": "poa-network",
"name": "POA Network",
"symbol": "POA",
"rank": "103",
"price_usd": "0.485053",
"price_btc": "0.00007032",
"24h_volume_usd": "20185000.0",
"market_cap_usd": "98941986.0",
"available_supply": "203981804.0",
"total_supply": "254473964.0",
"max_supply": null,
"percent_change_1h": "-0.66",
"percent_change_24h": "12.34",
"percent_change_7d": "49.15",
"last_updated": "1523473200"
}
]
"""
describe "format_data/1" do
test "returns valid tokens with valid data" do
expected_date = ~N[2018-04-11 19:00:00] |> DateTime.from_naive!("Etc/UTC")
expected = [
%Token{
available_supply: Decimal.new("203981804.0"),
total_supply: Decimal.new("254473964.0"),
btc_value: Decimal.new("0.00007032"),
id: "poa-network",
last_updated: expected_date,
market_cap_usd: Decimal.new("98941986.0"),
name: "POA Network",
symbol: "POA",
usd_value: Decimal.new("0.485053"),
volume_24h_usd: Decimal.new("20185000.0")
}
]
assert expected == CoinMarketCap.format_data(@json)
end
test "returns nothing when given bad data" do
bad_data = """
[{"id": "poa-network"}]
"""
assert [] = CoinMarketCap.format_data(bad_data)
end
end
end

@ -1,32 +1,41 @@
defmodule Explorer.ExchangeRates.Source.TokenBridgeTest do
use Explorer.DataCase
alias Explorer.ExchangeRates.Source.CoinGecko
alias Explorer.ExchangeRates.Source.TokenBridge
alias Explorer.ExchangeRates.Token
alias Plug.Conn
@json "#{File.cwd!()}/test/support/fixture/exchange_rates/coin_gecko.json"
|> File.read!()
|> Jason.decode!()
@json """
[
{
"id": "poa-network",
"name": "POA Network",
"symbol": "POA",
"rank": "103",
"price_usd": "0.485053",
"price_btc": "0.00007032",
"24h_volume_usd": "20185000.0",
"market_cap_usd": "98941986.0",
"available_supply": "203981804.0",
"total_supply": "254473964.0",
"max_supply": null,
"percent_change_1h": "-0.66",
"percent_change_24h": "12.34",
"percent_change_7d": "49.15",
"last_updated": "1523473200"
@json_btc_price """
{
"rates": {
"usd": {
"name": "US Dollar",
"unit": "$",
"value": 6547.418,
"type": "fiat"
}
}
]
}
"""
describe "format_data/1" do
test "bring a list with one %Token{}" do
setup do
bypass = Bypass.open()
Application.put_env(:explorer, CoinGecko, base_url: "http://localhost:#{bypass.port}")
{:ok, bypass: bypass}
end
test "bring a list with one %Token{}", %{bypass: bypass} do
Bypass.expect(bypass, "GET", "/exchange_rates", fn conn ->
Conn.resp(conn, 200, @json_btc_price)
end)
assert [%Token{}] = TokenBridge.format_data(@json)
end
end

@ -39,7 +39,8 @@ defmodule Explorer.DataCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Explorer.Chain.Cache.BlockNumber.setup()
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.BlockNumber.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())

@ -184,11 +184,10 @@ defmodule Indexer.Block.Fetcher do
end
defp update_block_cache(blocks) when is_list(blocks) do
max_block = Enum.max_by(blocks, fn block -> block.number end)
min_block = Enum.min_by(blocks, fn block -> block.number end)
{min_block, max_block} = Enum.min_max_by(blocks, & &1.number)
BlockNumber.update(max_block.number)
BlockNumber.update(min_block.number)
BlockNumber.update_all(max_block.number)
BlockNumber.update_all(min_block.number)
BlocksCache.update(blocks)
end
@ -269,7 +268,7 @@ defmodule Indexer.Block.Fetcher do
end
def async_import_internal_transactions(%{transactions: transactions}, EthereumJSONRPC.Geth) do
{_, max_block_number} = Chain.fetch_min_and_max_block_numbers()
max_block_number = Chain.fetch_max_block_number()
transactions
|> Enum.flat_map(fn

@ -162,7 +162,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end
defp latest_block_number do
BlockNumber.max_number()
BlockNumber.get_max()
end
defp stale_balance_window(block_number) do

@ -14,54 +14,57 @@ $ export NETWORK=POA
```
| Variable | Required | Description | Default | Version | Need recompile |
| --- | --- | --- | ---| --- | --- |
| `NETWORK`| :white_check_mark: | Environment variable for the main EVM network such as Ethereum Network or POA Network | POA Network | all | |
| `SUBNETWORK` | :white_check_mark: | Environment variable for the subnetwork such as Core or Sokol Network | Sokol Testnet | all | |
| `NETWORK_ICON` | :white_check_mark: | Environment variable for the main network icon or testnet icon. Two options are `_test_network_icon.html` and `_network_icon.html` | `_test_network_icon.html` | all | |
| `LOGO` | :white_check_mark: | Environment variable for the logo image location. The logo files names for different chains can be found [here](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/static/images) | /images/blockscout_logo.svg | all | |
| `ETHEREUM_JSONRPC_VARIANT` | :white_check_mark: | This environment variable is used to tell the application which RPC Client the node is using (i.e. Geth, Parity, or Ganache) | parity | all | |
| `ETHEREUM_JSONRPC_HTTP_URL` | :white_check_mark: | The RPC endpoint used to fetch blocks, transactions, receipts, tokens. | localhost:8545 | all | |
| `ETHEREUM_JSONRPC_TRACE_URL` | | The RPC endpoint specifically for the Geth/Parity client used by trace_block and trace_replayTransaction. This can be used to designate a tracing node. | localhost:8545 | all | |
| `ETHEREUM_JSONRPC_WS_URL` | :white_check_mark: | The WebSockets RPC endpoint used to subscribe to the `newHeads` subscription alerting the indexer to fetch new blocks. | ws://localhost:8546 | all | |
| `NETWORK_PATH` | | Used to set a network path other than what is displayed in the root directory. An example would be to add /eth/mainnet/ to the root directory. | (empty) | all | |
| `SECRET_KEY_BASE` | :white_check_mark: | Use mix phx.gen.secret to generate a new Secret Key Base string to protect production assets. | (empty) | all | |
| `CHECK_ORIGIN` | | Used to check the origin of requests when the origin header is present. It defaults to false. In case of true, it will check against the host value. | false | all | |
| `PORT` | :white_check_mark: | Default port the application runs on is 4000 | 4000 | all | |
| `COIN` | :white_check_mark: | The coin here is checked via the Coinmarketcap API to obtain USD prices on graphs and other areas of the UI | POA | all | |
| `METADATA_CONTRACT` | | This environment variable is specifically used by POA Network to obtain Validators information to display in the UI. | (empty) | all | |
| `VALIDATORS_CONTRACT` | | This environment variable is specifically used by POA Network to obtain the Emission Fund contract. | (empty) | all | |
| `SUPPLY_MODULE` | | This environment variable is used by the xDai Chain in order to tell the application how to calculate the total supply of the chain. | false | all | |
| `SOURCE_MODULE` | | This environment variable is used to calculate the exchange rate and is specifically used by the xDai Chain. | false | all | |
| `DATABASE_URL` | | Production environment variable to define the Database endpoint. | (empty) | all | |
| `POOL_SIZE` | | Production environment variable to define the number of database connections allowed. | 20 | all | |
| `ECTO_USE_SSL` | | Production environment variable to use SSL on Ecto queries. | true | all | |
| `DATADOG_HOST` | | Host configuration setting for [Datadog integration](https://docs.datadoghq.com/integrations/) | (empty) | all | |
| `DATADOG_PORT` | | Port configuration setting for [Datadog integration](https://docs.datadoghq.com/integrations/). | (empty} | all | |
| `SPANDEX_BATCH_SIZE` | | [Spandex](https://github.com/spandex-project/spandex) and Datadog configuration setting. | (empty) | all |
| `SPANDEX_SYNC_THRESHOLD` | | [Spandex](https://github.com/spandex-project/spandex) and Datadog configuration setting. | (empty) | all | |
| `HEART_BEAT_TIMEOUT` | | Production environment variable to restart the application in the event of a crash. | 30 | all | |
| `HEART_COMMAND` | | Production environment variable to restart the application in the event of a crash. | systemctl restart explorer.service | all | |
| `BLOCKSCOUT_VERSION` | | Added to the footer to signify the current BlockScout version. | (empty) | v1.3.4+ | |
| `RELEASE_LINK` | | The link to Blockscout release notes in the footer. | <u>https: //github.com/poanetwork/</u> <br /><u>blockscout/releases/</u> <br /> <u>tag/${BLOCKSCOUT_VERSION}</u> | v1.3.5+ | |
| `ELIXIR_VERSION` | | Elixir version to install on the node before Blockscout deploy. | (empty) | all | |
| `BLOCK_TRANSFORMER` | | Transformer for blocks: base or clique. | base | v1.3.4+ | |
| `GRAPHIQL_TRANSACTION` | | Default transaction in query to GraphiQL. | (empty) | v1.2.0+ | :white_check_mark: |
| `FIRST_BLOCK` | | The block number, where indexing begins from. | 0 | v1.3.8+ | |
| `LAST_BLOCK` | | The block number, where indexing stops. | (empty) | v2.0.3+ | |
| `TXS_COUNT_CACHE_PERIOD` | | Interval in seconds to restart the task, which calculates the total txs count. | 60 * 60 * 2 | v1.3.9+ | |
| `ADDRESS_WITH_BALANCES` <br /> `_UPDATE_INTERVAL`| | Interval in seconds to restart the task, which calculates addresses with balances. | 30 * 60 | v1.3.9+ | |
| `LINK_TO_OTHER_EXPLORERS` | | true/false. If true, links to other explorers are added in the footer | (empty) | v1.3.0+ | |
| `COINMARKETCAP_PAGES` | | the number of pages on coinmarketcap to list in order to find token's price | 10 | v1.3.10+ | |
| `SUPPORTED_CHAINS` | | Array of supported chains that displays in the footer and in the chains dropdown. This var was introduced in this PR [#1900](https://github.com/poanetwork/blockscout/pull/1900) and looks like an array of JSON objects. | (empty) | v2.0.0+ | |
| `BLOCK_COUNT_CACHE_PERIOD ` | | time to live of cache in seconds. This var was introduced in [#1876](https://github.com/poanetwork/blockscout/pull/1876) | 600 | v2.0.0+ | |
| `ALLOWED_EVM_VERSIONS ` | | the comma-separated list of allowed EVM versions for contracts verification. This var was introduced in [#1964](https://github.com/poanetwork/blockscout/pull/1964) | "homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg" | v2.0.0+ | |
| `AVERAGE_BLOCK_CACHE_PERIOD` | | Update of average block cache, in seconds | 30 minutes | v2.0.2+ |
| `MARKET_HISTORY_CACHE_PERIOD` | | Update of market history cache, in seconds | 6 hours | v2.0.2+ |
| `DISABLE_WEBAPP` | | If `true`, endpoints to webapp are hidden (compile-time) | `false` | v2.0.3+ | :white_check_mark: |
| `DISABLE_READ_API` | | If `true`, read-only endpoints to API are hidden (compile-time) | `false` | v2.0.3+ | :white_check_mark: |
| `DISABLE_WRITE_API` | | If `true`, write endpoints to API are hidden (compile-time) | `false` | v2.0.3+ | :white_check_mark: |
| `DISABLE_INDEXER` | | If `true`, indexer application doesn't run | `false` | v2.0.3+ | :white_check_mark: |
| `WEBAPP_URL` | | Link to web application instance, e.g. `http://host/path` | (empty) | v2.0.3+ | |
| `API_URL` | | Link to API instance, e.g. `http://host/path` | (empty) | v2.0.3+ | |
| `CHAIN_SPEC_PATH` | | Chain specification path (absolute file system path or url) to import block emission reward ranges and genesis account balances from | (empty) | master | |
| Variable | Required | Description | Default | Version | Need recompile | Deprecated in Version |
| --- | --- | --- | ---| --- | --- | --- |
| `NETWORK`| :white_check_mark: | Environment variable for the main EVM network such as Ethereum Network or POA Network | POA Network | all | | |
| `SUBNETWORK` | :white_check_mark: | Environment variable for the subnetwork such as Core or Sokol Network | Sokol Testnet | all | | |
| `NETWORK_ICON` | :white_check_mark: | Environment variable for the main network icon or testnet icon. Two options are `_test_network_icon.html` and `_network_icon.html` | `_test_network_icon.html` | all | | |
| `LOGO` | :white_check_mark: | Environment variable for the logo image location. The logo files names for different chains can be found [here](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/static/images) | /images/blockscout_logo.svg | all | | |
| `ETHEREUM_JSONRPC_VARIANT` | :white_check_mark: | This environment variable is used to tell the application which RPC Client the node is using (i.e. Geth, Parity, or Ganache) | parity | all | | |
| `ETHEREUM_JSONRPC_HTTP_URL` | :white_check_mark: | The RPC endpoint used to fetch blocks, transactions, receipts, tokens. | localhost:8545 | all | | |
| `ETHEREUM_JSONRPC_TRACE_URL` | | The RPC endpoint specifically for the Geth/Parity client used by trace_block and trace_replayTransaction. This can be used to designate a tracing node. | localhost:8545 | all | | |
| `ETHEREUM_JSONRPC_WS_URL` | :white_check_mark: | The WebSockets RPC endpoint used to subscribe to the `newHeads` subscription alerting the indexer to fetch new blocks. | ws://localhost:8546 | all | | |
| `NETWORK_PATH` | | Used to set a network path other than what is displayed in the root directory. An example would be to add /eth/mainnet/ to the root directory. | (empty) | all | | |
| `SECRET_KEY_BASE` | :white_check_mark: | Use mix phx.gen.secret to generate a new Secret Key Base string to protect production assets. | (empty) | all | | |
| `CHECK_ORIGIN` | | Used to check the origin of requests when the origin header is present. It defaults to false. In case of true, it will check against the host value. | false | all | | |
| `PORT` | :white_check_mark: | Default port the application runs on is 4000 | 4000 | all | | |
| `COIN` | :white_check_mark: | The coin here is checked via the CoinGecko API to obtain USD prices on graphs and other areas of the UI | POA | all | | |
| `METADATA_CONTRACT` | | This environment variable is specifically used by POA Network to obtain Validators information to display in the UI. | (empty) | all | | |
| `VALIDATORS_CONTRACT` | | This environment variable is specifically used by POA Network to obtain the Emission Fund contract. | (empty) | all | | |
| `SUPPLY_MODULE` | | This environment variable is used by the xDai Chain in order to tell the application how to calculate the total supply of the chain. | false | all | | |
| `SOURCE_MODULE` | | This environment variable is used to calculate the exchange rate and is specifically used by the xDai Chain. | false | all | | |
| `DATABASE_URL` | | Production environment variable to define the Database endpoint. | (empty) | all | | |
| `POOL_SIZE` | | Production environment variable to define the number of database connections allowed. | 20 | all | | |
| `ECTO_USE_SSL` | | Production environment variable to use SSL on Ecto queries. | true | all | | |
| `DATADOG_HOST` | | Host configuration setting for [Datadog integration](https://docs.datadoghq.com/integrations/) | (empty) | all | | |
| `DATADOG_PORT` | | Port configuration setting for [Datadog integration](https://docs.datadoghq.com/integrations/). | (empty} | all | | |
| `SPANDEX_BATCH_SIZE` | | [Spandex](https://github.com/spandex-project/spandex) and Datadog configuration setting. | (empty) | all | |
| `SPANDEX_SYNC_THRESHOLD` | | [Spandex](https://github.com/spandex-project/spandex) and Datadog configuration setting. | (empty) | all | | |
| `HEART_BEAT_TIMEOUT` | | Production environment variable to restart the application in the event of a crash. | 30 | all | | |
| `HEART_COMMAND` | | Production environment variable to restart the application in the event of a crash. | systemctl restart explorer.service | all | | |
| `BLOCKSCOUT_VERSION` | | Added to the footer to signify the current BlockScout version. | (empty) | v1.3.4+ | | |
| `RELEASE_LINK` | | The link to Blockscout release notes in the footer. | <u>https: //github.com/poanetwork/</u> <br /><u>blockscout/releases/</u> <br /> <u>tag/${BLOCKSCOUT_VERSION}</u> | v1.3.5+ | | |
| `ELIXIR_VERSION` | | Elixir version to install on the node before Blockscout deploy. | (empty) | all | | |
| `BLOCK_TRANSFORMER` | | Transformer for blocks: base or clique. | base | v1.3.4+ | | |
| `GRAPHIQL_TRANSACTION` | | Default transaction in query to GraphiQL. | (empty) | v1.2.0+ | :white_check_mark: | |
| `FIRST_BLOCK` | | The block number, where indexing begins from. | 0 | v1.3.8+ | | |
| `LAST_BLOCK` | | The block number, where indexing stops. | (empty) | v2.0.3+ | | |
| `TXS_COUNT_CACHE_PERIOD` | | Interval in seconds to restart the task, which calculates the total txs count. | 60 * 60 * 2 | v1.3.9+ | | |
| `ADDRESS_WITH_BALANCES` <br /> `_UPDATE_INTERVAL`| | Interval in seconds to restart the task, which calculates addresses with balances. | 30 * 60 | v1.3.9+ | | |
| `LINK_TO_OTHER_EXPLORERS` | | true/false. If true, links to other explorers are added in the footer | (empty) | v1.3.0+ | | |
| `COINMARKETCAP_PAGES` | | the number of pages on coinmarketcap to list in order to find token's price | 10 | v1.3.10+ | | v2.0.4 |
| `SUPPORTED_CHAINS` | | Array of supported chains that displays in the footer and in the chains dropdown. This var was introduced in this PR [#1900](https://github.com/poanetwork/blockscout/pull/1900) and looks like an array of JSON objects. | (empty) | v2.0.0+ | | |
| `BLOCK_COUNT_CACHE_PERIOD ` | | time to live of cache in seconds. This var was introduced in [#1876](https://github.com/poanetwork/blockscout/pull/1876) | 600 | v2.0.0+ | | |
| `ALLOWED_EVM_VERSIONS ` | | the comma-separated list of allowed EVM versions for contracts verification. This var was introduced in [#1964](https://github.com/poanetwork/blockscout/pull/1964) | "homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg" | v2.0.0+ | | |
| `AVERAGE_BLOCK_CACHE_PERIOD` | | Update of average block cache, in seconds | 30 minutes | v2.0.2+ | |
| `MARKET_HISTORY_CACHE_PERIOD` | | Update of market history cache, in seconds | 6 hours | v2.0.2+ | |
| `DISABLE_WEBAPP` | | If `true`, endpoints to webapp are hidden (compile-time) | `false` | v2.0.3+ | :white_check_mark: | |
| `DISABLE_READ_API` | | If `true`, read-only endpoints to API are hidden (compile-time) | `false` | v2.0.3+ | :white_check_mark: | |
| `DISABLE_WRITE_API` | | If `true`, write endpoints to API are hidden (compile-time) | `false` | v2.0.3+ | :white_check_mark: | |
| `DISABLE_INDEXER` | | If `true`, indexer application doesn't run | `false` | v2.0.3+ | :white_check_mark: | |
| `WEBAPP_URL` | | Link to web application instance, e.g. `http://host/path` | (empty) | v2.0.3+ | | |
| `API_URL` | | Link to API instance, e.g. `http://host/path` | (empty) | v2.0.3+ | | |
| `CHAIN_SPEC_PATH` | | Chain specification path (absolute file system path or url) to import block emission reward ranges and genesis account balances from | (empty) | v2.0.4+ | | |
| `COIN_GECKO_ID` | | CoinGecko coin id required for fetching an exchange rate | poa-network | v2.0.4+ | | |
| `EMISSION_FORMAT` | | Should be set to `POA` if you have block emission indentical to POA Network. This env var is used only if `CHAIN_SPEC_PATH` is set | `STANDARD` | v2.0.4+ | | |
| `REWARDS_CONTRACT_ADDRESS` | | Emission rewards contract address. This env var is used only if `EMISSION_FORMAT` is set to `POA` | `0xeca443e8e1ab29971a45a9c57a6a9875701698a5` | v2.0.4+ | | |

Loading…
Cancel
Save