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. 29
      README.md
  3. 1
      apps/block_scout_web/assets/css/_images-preload.scss
  4. 12
      apps/block_scout_web/assets/css/app.scss
  5. 37
      apps/block_scout_web/assets/css/components/_card.scss
  6. 8
      apps/block_scout_web/assets/css/components/_custom_tooltips_block_details.scss
  7. 43
      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. 6
      apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
  44. 9
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  45. 23
      apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
  46. 9
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  47. 6
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  48. 4
      apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex
  49. 1451
      apps/block_scout_web/priv/gettext/default.pot
  50. 1460
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  51. 20
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  52. 4
      apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
  53. 4
      apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs
  54. 2
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  55. 4
      apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs
  56. 8
      apps/block_scout_web/test/block_scout_web/views/address_coin_balance_view_test.exs
  57. 47
      apps/block_scout_web/test/block_scout_web/views/address_contract_view_test.exs
  58. 44
      apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
  59. 2
      apps/block_scout_web/test/block_scout_web/views/block_view_test.exs
  60. 7
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  61. 22
      apps/explorer/config/config.exs
  62. 13
      apps/explorer/lib/explorer/application.ex
  63. 130
      apps/explorer/lib/explorer/chain.ex
  64. 2
      apps/explorer/lib/explorer/chain/address.ex
  65. 5
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  66. 150
      apps/explorer/lib/explorer/chain/cache/block_count.ex
  67. 89
      apps/explorer/lib/explorer/chain/cache/block_number.ex
  68. 4
      apps/explorer/lib/explorer/chain/cache/blocks.ex
  69. 47
      apps/explorer/lib/explorer/chain/cache/net_version.ex
  70. 158
      apps/explorer/lib/explorer/chain/cache/transaction_count.ex
  71. 4
      apps/explorer/lib/explorer/chain/cache/transactions.ex
  72. 217
      apps/explorer/lib/explorer/chain/map_cache.ex
  73. 42
      apps/explorer/lib/explorer/chain/ordered_cache.ex
  74. 39
      apps/explorer/lib/explorer/chain/smart_contract.ex
  75. 4
      apps/explorer/lib/explorer/chain/supply/exchange_rate.ex
  76. 2
      apps/explorer/lib/explorer/chain/supply/rsk.ex
  77. 44
      apps/explorer/lib/explorer/chain/transaction.ex
  78. 46
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  79. 89
      apps/explorer/lib/explorer/chain_spec/poa/importer.ex
  80. 32
      apps/explorer/lib/explorer/exchange_rates/source.ex
  81. 39
      apps/explorer/lib/explorer/exchange_rates/source/coin_gecko.ex
  82. 52
      apps/explorer/lib/explorer/exchange_rates/source/coin_market_cap.ex
  83. 12
      apps/explorer/lib/explorer/exchange_rates/source/token_bridge.ex
  84. 26
      apps/explorer/test/explorer/chain/cache/block_count_test.exs
  85. 50
      apps/explorer/test/explorer/chain/cache/block_number_test.exs
  86. 4
      apps/explorer/test/explorer/chain/cache/blocks_test.exs
  87. 26
      apps/explorer/test/explorer/chain/cache/transaction_count_test.exs
  88. 24
      apps/explorer/test/explorer/chain_test.exs
  89. 57
      apps/explorer/test/explorer/exchange_rates/source/coin_gecko_test.exs
  90. 59
      apps/explorer/test/explorer/exchange_rates/source/coin_market_cap_test.exs
  91. 47
      apps/explorer/test/explorer/exchange_rates/source/token_bridge_test.exs
  92. 3
      apps/explorer/test/support/data_case.ex
  93. 1806
      apps/explorer/test/support/fixture/exchange_rates/coin_gecko.json
  94. 9
      apps/indexer/lib/indexer/block/fetcher.ex
  95. 2
      apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
  96. 105
      docs/env-variables.md

@ -1,10 +1,13 @@
## Current ## Current
### Features ### 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 - [#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 ### Fixes
- [#2682](https://github.com/poanetwork/blockscout/pull/2682) - Use Task.start instead of Task.async in caches
### Chore ### Chore

@ -28,20 +28,21 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc
## Supported Projects ## Supported Projects
| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** | | **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** | **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) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) | [Celo Testnet](https://alfajores-blockscout.celo-testnet.org/) |
| [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) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | [Matic Testnet](https://explorer.testnet2.matic.network/) |
| [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) | [LUKSO L14 Testnet](https://blockscout.com/lukso/l14) | [Kotti Testnet](https://kottiexplorer.ethernode.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) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.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) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | |
| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) | | [xDai Chain](https://blockscout.com/poa/dai) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) | |
| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) | | | | [PIRL](http://pirl.es/) | |
| | | [SpringChain](https://explorer.springrole.com/) | | | | [SafeChain](https://explorer.safechain.io) | |
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | | | | [SpringChain](https://explorer.springrole.com/) | |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) | | | | [Tenda](https://tenda.network) | |
| | | [Tenda](https://tenda.network) | | | | [Loom](http://plasma-blockexplorer.dappchains.com/) | |
| | | [GoJoy Chain](https://gojoychain.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). Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).

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

@ -1,12 +1,10 @@
@import "./mixins"; @import "./mixins";
/* Phoenix flash messages */ /* Phoenix flash messages */
.alert:empty { .alert:empty {
display: none; display: none;
} }
/* This file is for your main application css. */ /* This file is for your main application css. */
// Font Awesome // Font Awesome
@ -15,10 +13,13 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "fa-brands"; @import "fa-brands";
@import "fa-regular"; @import "fa-regular";
@import "fa-solid"; @import "fa-solid";
// Bootstrap Core CSS // Bootstrap Core CSS
@import "node_modules/bootstrap/scss/functions"; @import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/mixins"; @import "node_modules/bootstrap/scss/mixins";
@import "theme/variables"; @import "theme/variables";
@import "node_modules/bootstrap/scss/root"; @import "node_modules/bootstrap/scss/root";
@import "node_modules/bootstrap/scss/reboot"; @import "node_modules/bootstrap/scss/reboot";
@import "node_modules/bootstrap/scss/grid"; @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/position";
@import "node_modules/bootstrap/scss/utilities/borders"; @import "node_modules/bootstrap/scss/utilities/borders";
@import "node_modules/bootstrap/scss/progress"; @import "node_modules/bootstrap/scss/progress";
// Bootstrap Components // Bootstrap Components
@import "node_modules/bootstrap/scss/alert"; @import "node_modules/bootstrap/scss/alert";
@import "node_modules/bootstrap/scss/badge"; @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/pagination";
@import "node_modules/bootstrap/scss/tables"; @import "node_modules/bootstrap/scss/tables";
@import "node_modules/bootstrap/scss/transitions"; @import "node_modules/bootstrap/scss/transitions";
// Code highlight // Code highlight
@import "node_modules/highlight.js/styles/default"; @import "node_modules/highlight.js/styles/default";
//Custom theme //Custom theme
@import "theme/fonts"; @import "theme/fonts";
// Custom SCSS // Custom SCSS
@import "layout"; @import "layout";
@import "typography"; @import "typography";
@ -109,7 +114,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/radio_big"; @import "components/radio_big";
@import "components/btn_no_border"; @import "components/btn_no_border";
@import "components/custom_tooltips_block_details"; @import "components/custom_tooltips_block_details";
@import "components/_erc721_token_image_container";
@import "theme/dark-theme"; @import "theme/dark-theme";
@ -122,5 +126,3 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
darkprimary: $dark-primary; darkprimary: $dark-primary;
darksecondary: $dark-secondary; darksecondary: $dark-secondary;
} }

@ -7,19 +7,22 @@ $card-background-1: $primary !default;
$card-background-1-text-color: #fff !default; $card-background-1-text-color: #fff !default;
$card-tab-icon-color: #20b760 !default; $card-tab-icon-color: #20b760 !default;
$card-tab-icon-color-active: #fff !default; $card-tab-icon-color-active: #fff !default;
.card { .card {
background-color: $card-background-color; background-color: $card-background-color;
border-radius: $card-default-border-radius; border-radius: $card-default-border-radius;
border: none; border: none;
box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5); box-shadow: 0 0 30px 0 rgba(202, 199, 226, 0.5);
margin-bottom: $common-container-margin; margin-bottom: $common-container-margin;
.block-details-row { .block-details-row {
flex-direction: row; flex-direction: row;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
flex-direction: column; flex-direction: column;
} }
.block-detail-el { .block-detail-el {
&+.block-detail-el { & + .block-detail-el {
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
margin-top: 6px; margin-top: 6px;
} }
@ -31,6 +34,7 @@ $card-tab-icon-color-active: #fff !default;
.card-background-1 { .card-background-1 {
background-color: $card-background-1; background-color: $card-background-1;
color: $card-background-1-text-color; color: $card-background-1-text-color;
a:not(.dropdown-item), a:not(.dropdown-item),
a:not(.dropdown-item):hover { a:not(.dropdown-item):hover {
color: $card-background-1-text-color; color: $card-background-1-text-color;
@ -41,6 +45,7 @@ $card-tab-icon-color-active: #fff !default;
background: transparent; background: transparent;
border-bottom: 1px solid $base-border-color; border-bottom: 1px solid $base-border-color;
padding: $card-vertical-padding $card-horizontal-padding; padding: $card-vertical-padding $card-horizontal-padding;
&-tabs { &-tabs {
margin: (-$card-spacer-y) (-$card-spacer-x); margin: (-$card-spacer-y) (-$card-spacer-x);
} }
@ -51,26 +56,33 @@ $card-tab-icon-color-active: #fff !default;
font-weight: normal; font-weight: normal;
line-height: 1.2rem; line-height: 1.2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
&.lg-card-title { &.lg-card-title {
@media (max-width: 374px) { @media (max-width: 374px) {
font-size: 13px; font-size: 13px;
} }
} }
&.margin-bottom-md { &.margin-bottom-md {
margin-bottom: 25px; margin-bottom: 25px;
} }
&.margin-bottom-sm { &.margin-bottom-sm {
margin-bottom: 15px; margin-bottom: 15px;
} }
&.margin-bottom-xs { &.margin-bottom-xs {
margin-bottom: 10px; margin-bottom: 10px;
} }
&.margin-bottom-0 { &.margin-bottom-0 {
margin-bottom: 0; margin-bottom: 0;
} }
.card-title-container & { .card-title-container & {
line-height: 1.2; line-height: 1.2;
margin: 0; margin: 0;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
margin-bottom: 25px; margin-bottom: 25px;
} }
@ -83,6 +95,7 @@ $card-tab-icon-color-active: #fff !default;
font-weight: normal; font-weight: normal;
line-height: 1.2; line-height: 1.2;
margin: 0 0 30px; margin: 0 0 30px;
&.margin-bottom-0 { &.margin-bottom-0 {
margin-bottom: 0; margin-bottom: 0;
} }
@ -93,6 +106,7 @@ $card-tab-icon-color-active: #fff !default;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 25px $card-horizontal-padding; padding: 25px $card-horizontal-padding;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
flex-direction: column; flex-direction: column;
} }
@ -102,17 +116,22 @@ $card-tab-icon-color-active: #fff !default;
align-items: center; align-items: center;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
flex-direction: column; flex-direction: column;
} }
.card-title-control { .card-title-control {
margin-right: 20px; margin-right: 20px;
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
} }
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
margin-bottom: 20px; margin-bottom: 20px;
margin-right: 0; margin-right: 0;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@ -136,7 +155,8 @@ $card-tab-icon-color-active: #fff !default;
} }
.card-chain-blocks { .card-chain-blocks {
height: auto; min-height: 233px;
max-height: auto;
[class*="col-"]:last-child { [class*="col-"]:last-child {
.tile { .tile {
margin-bottom: 0; margin-bottom: 0;
@ -145,7 +165,9 @@ $card-tab-icon-color-active: #fff !default;
} }
.card-chain-transactions { .card-chain-transactions {
height: auto; min-height: 664px;
max-height: auto;
.tile { .tile {
margin-bottom: 0; margin-bottom: 0;
} }
@ -159,6 +181,7 @@ $card-tab-icon-color-active: #fff !default;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
overflow: hidden; overflow: hidden;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
flex-direction: column; flex-direction: column;
} }
@ -176,31 +199,38 @@ $card-tab-icon-color-active: #fff !default;
padding: 0 25px; padding: 0 25px;
text-align: center; text-align: center;
transition: $transition-cont; transition: $transition-cont;
&:hover { &:hover {
background-color: rgba($card-tab-active, .15); background-color: rgba($card-tab-active, .15);
color: $card-tab-active; color: $card-tab-active;
text-decoration: none; text-decoration: none;
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
display: none; display: none;
width: 100%; width: 100%;
} }
.fa-check-circle { .fa-check-circle {
color: $card-tab-icon-color; color: $card-tab-icon-color;
margin-left: 6px; margin-left: 6px;
} }
&.active { &.active {
background-color: $card-tab-active; background-color: $card-tab-active;
color: #fff; color: #fff;
cursor: default; cursor: default;
text-decoration: none; text-decoration: none;
.fa-check-circle { .fa-check-circle {
color: $card-tab-icon-color-active; color: $card-tab-icon-color-active;
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
order: -1; order: -1;
&::after { &::after {
border-bottom: 0; border-bottom: 0;
border-left: 0.3em solid transparent; border-left: 0.3em solid transparent;
@ -212,6 +242,7 @@ $card-tab-icon-color-active: #fff !default;
margin-left: 10px; margin-left: 10px;
width: 0; width: 0;
} }
&.noCaret::after { &.noCaret::after {
display: none; display: none;
} }

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

@ -1,7 +1,10 @@
$dashboard-banner-gradient-start: $primary !default; $dashboard-banner-gradient-start: $primary !default;
$dashboard-banner-gradient-end: lighten( $dashboard-banner-gradient-start, 5%) !default; $dashboard-banner-gradient-end: lighten(
$dashboard-banner-network-plain-container-background-color: lighten( $dashboard-banner-gradient-end, 5%) !default; $dashboard-banner-gradient-start,
$dashboard-line-color-price: lighten( $dashboard-banner-gradient-end, 5%) !default; 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-line-color-market: $secondary !default;
$dashboard-stats-item-label-color: #fff !default; $dashboard-stats-item-label-color: #fff !default;
$dashboard-stats-item-value-color: rgba(#fff, 0.8) !default; $dashboard-stats-item-value-color: rgba(#fff, 0.8) !default;
@ -10,6 +13,7 @@ $dashboard-banner-chart-legend-value-color: $dashboard-stats-item-value-color !d
$dashboard-stats-item-border-color: $primary !default; $dashboard-stats-item-border-color: $primary !default;
$dashboard-banner-network-plain-container-height: 205px; $dashboard-banner-network-plain-container-height: 205px;
$dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !default; $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !default;
.dashboard-banner-container { .dashboard-banner-container {
@include gradient-container(); @include gradient-container();
margin-bottom: 3rem; margin-bottom: 3rem;
@ -30,6 +34,7 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
justify-content: space-between; justify-content: space-between;
position: relative; position: relative;
z-index: 9; z-index: 9;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
flex-direction: column; flex-direction: column;
} }
@ -38,6 +43,7 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
.dashboard-banner-network-graph { .dashboard-banner-network-graph {
flex-grow: 1; flex-grow: 1;
padding: 15px 0 0 0; padding: 15px 0 0 0;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -50,13 +56,15 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
margin: 0 0 35px 0; margin: 0 0 35px 0;
max-width: 350px; max-width: 350px;
position: relative; position: relative;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
flex-grow: 0; flex-grow: 0;
margin-bottom: 20px; margin-bottom: 20px;
margin-top: auto; margin-top: auto;
max-width: 100%; max-width: 100%;
} }
>canvas {
> canvas {
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
width: 100%; width: 100%;
@ -65,23 +73,27 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
.dashboard-banner-chart-legend { .dashboard-banner-chart-legend {
display: flex; display: flex;
/* grid-template-columns: 1fr 1fr; */ grid-template-columns: 1fr 1fr;
padding-bottom: 12px; padding-bottom: 12px;
.dashboard-banner-chart-legend-item { .dashboard-banner-chart-legend-item {
padding-bottom: 3px; padding-bottom: 3px;
padding-left: 12px; padding-left: 12px;
padding-top: 3px; padding-top: 3px;
position: relative; position: relative;
padding-right: 60px; padding-right: 60px;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
@media (max-width: 599px) { @media (max-width: 599px) {
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
flex-direction: column; flex-direction: column;
} }
&::before { &::before {
border-radius: 2px; border-radius: 2px;
content: ""; content: "";
@ -91,13 +103,16 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
top: 0; top: 0;
width: 4px; width: 4px;
} }
&:nth-child(1)::before { &:nth-child(1)::before {
background-color: $dashboard-line-color-price; background-color: $dashboard-line-color-price;
} }
&:nth-child(2)::before { &:nth-child(2)::before {
background-color: $dashboard-line-color-market; background-color: $dashboard-line-color-market;
} }
} }
.dashboard-banner-chart-legend-label { .dashboard-banner-chart-legend-label {
color: $dashboard-banner-chart-legend-label-color; color: $dashboard-banner-chart-legend-label-color;
display: block; display: block;
@ -105,14 +120,17 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
font-weight: 600; font-weight: 600;
line-height: 1.2; line-height: 1.2;
margin: 0 0 5px; margin: 0 0 5px;
@media (max-width: 374px) { @media (max-width: 374px) {
position: relative; position: relative;
top: -2px; top: -2px;
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
margin: 0 5px 0 0; margin: 0 5px 0 0;
} }
} }
.dashboard-banner-chart-legend-value { .dashboard-banner-chart-legend-value {
color: $dashboard-banner-chart-legend-value-color; color: $dashboard-banner-chart-legend-value-color;
display: block; display: block;
@ -135,10 +153,12 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
padding: 30px 0 30px 60px; padding: 30px 0 30px 60px;
width: 750px; width: 750px;
position: relative; position: relative;
@include media-breakpoint-down(lg) { @include media-breakpoint-down(lg) {
margin-top: 50px; margin-top: 15px;
width: 550px; width: 550px;
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
border-top-right-radius: 10px; border-top-right-radius: 10px;
height: auto; height: auto;
@ -149,9 +169,11 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
width: 250px; width: 250px;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2);
} }
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
width: 100%; width: 100%;
} }
&::after { &::after {
background-color: $dashboard-banner-network-plain-container-background-color; background-color: $dashboard-banner-network-plain-container-background-color;
bottom: 0; bottom: 0;
@ -164,6 +186,7 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
z-index: -1; z-index: -1;
box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 0 35px 0 rgba(0, 0, 0, 0.2);
border-top-left-radius: 10px; border-top-left-radius: 10px;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
display: none; display: none;
} }
@ -174,25 +197,31 @@ $dashboard-banner-chart-axis-font-color: $dashboard-stats-item-value-color !defa
column-gap: 25px; column-gap: 25px;
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
@include media-breakpoint-down(lg) { @include media-breakpoint-down(lg) {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
row-gap: 20px; row-gap: 20px;
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
row-gap: 20px; row-gap: 20px;
} }
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
column-gap: 10px; column-gap: 10px;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
@include stats-item( $dashboard-stats-item-border-color, $dashboard-stats-item-label-color, $dashboard-stats-item-value-color);
@include stats-item($dashboard-stats-item-border-color, $dashboard-stats-item-label-color, $dashboard-stats-item-value-color);
.dashboard-banner-network-stats-item { .dashboard-banner-network-stats-item {
@media (max-width: 374px) { @media (max-width: 374px) {
padding-left: calc(0.6rem + 4px); padding-left: calc(0.6rem + 4px);
padding-right: 0.5rem; padding-right: 0.5rem;
} }
} }
.dashboard-banner-network-stats-value { .dashboard-banner-network-stats-value {
@media (max-width: 374px) { @media (max-width: 374px) {
font-size: 0.9rem; 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 { &-lukso-l14-testnet {
background-image: url(/images/network-selector-icons/lukso-l14-testnet.svg) 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 { .network-selector-item-title {

@ -562,3 +562,15 @@ $cube-quantity: 5;
transform: scale(0); 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;
}

@ -44,3 +44,10 @@
} }
} }
} }
.block-detail-number {
width: 25%;
@include media-breakpoint-down(sm) {
width: 60%;
}
}

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

@ -1,5 +1,6 @@
@import "theme/base_variables"; @import "theme/base_variables";
@import "neutral_variables"; @import "neutral_variables";
// @import "xusdt_variables";
// @import "dai_variables"; // @import "dai_variables";
// @import "ethereum_classic_variables"; // @import "ethereum_classic_variables";
// @import "ethereum_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) clickedButton.prop('disabled', false)
} }
function dropDomain (url) {
return new URL(url).pathname
}
// Show 'Try it out' UI for a module/action. // Show 'Try it out' UI for a module/action.
$('button[data-selector*="btn-try-api"]').click(event => { $('button[data-selector*="btn-try-api"]').click(event => {
const clickedButton = $(event.target) const clickedButton = $(event.target)
@ -124,7 +128,7 @@ $('button[data-try-api-ui-button-type="execute"]').click(event => {
} }
$.ajax({ $.ajax({
url: `/api${query}`, url: dropDomain(composeRequestUrl(query)),
success: (_data, _status, xhr) => { success: (_data, _status, xhr) => {
handleSuccess(query, xhr, clickedButton) handleSuccess(query, xhr, clickedButton)
}, },

@ -1,7 +1,8 @@
import $ from 'jquery' import $ from 'jquery'
function composeCurlCommand (data) { 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) { 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 => { $('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
const clickedButton = $(event.target) const clickedButton = $(event.target)
const module = clickedButton.attr('data-module') 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 inputs = $(`input[data-selector="${module}-${action}-try-api-ui"]`)
const params = $.map(inputs, parseInput) const params = $.map(inputs, parseInput)
const formData = wrapJsonRpc(action, params) 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...' 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) clickedButton.prop('disabled', true)
@ -60,8 +64,10 @@ $('button[data-try-eth-api-ui-button-type="execute"]').click(event => {
clickedButton.html(loadingText) clickedButton.html(loadingText)
} }
const url = $('[data-endpoint-url]').attr('data-endpoint-url')
$.ajax({ $.ajax({
url: '/api/eth_rpc', url: dropDomain(url),
type: 'POST', type: 'POST',
data: JSON.stringify(formData), data: JSON.stringify(formData),
dataType: 'json', dataType: 'json',

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

@ -30,7 +30,7 @@
"highlightjs-solidity": "^1.0.6", "highlightjs-solidity": "^1.0.6",
"humps": "^2.0.1", "humps": "^2.0.1",
"jquery": "^3.4.0", "jquery": "^3.4.0",
"lodash": "^4.17.13", "lodash": "^4.17.15",
"moment": "^2.22.1", "moment": "^2.22.1",
"nanomorph": "^5.1.3", "nanomorph": "^5.1.3",
"numeral": "^2.0.6", "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"> <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="#202D6A" fill-rule="evenodd" d="M0 30V0h30v30H0z"/>
<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="#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> </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 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] import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.AddressCoinBalanceView alias BlockScoutWeb.AddressCoinBalanceView
@ -60,12 +60,14 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
def index(conn, %{"address_id" => address_hash_string}) do def index(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, validation_count} = transaction_and_validation_count(address_hash)
render(conn, "index.html", render(conn, "index.html",
address: address, address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address), coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(), exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address_hash), transaction_count: transaction_count,
validation_count: validation_count(address_hash), validation_count: validation_count,
current_path: current_path(conn) current_path: current_path(conn)
) )
else else

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

@ -69,6 +69,34 @@ defmodule BlockScoutWeb.AddressController do
redirect(conn, to: address_transaction_path(conn, :index, id)) redirect(conn, to: address_transaction_path(conn, :index, id))
end end
def transaction_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 def transaction_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
Chain.total_transactions_sent_by_address(address_hash) Chain.total_transactions_sent_by_address(address_hash)
end end

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

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

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

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

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

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

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

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

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

@ -66,7 +66,7 @@
</button> </button>
</div> </div>
<div class="tile tile-muted mb-4"> <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> </div>
</section> </section>

@ -4,9 +4,9 @@
<h3 class="api-doc-list-item-title"><%= @action %></h3> <h3 class="api-doc-list-item-title"><%= @action %></h3>
<p class="api-doc-list-item-contents"><%= raw @info.notes %></p> <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" <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> </span>
<p class="api-doc-list-item-text"> <p class="api-doc-list-item-text">
<div class="tile tile-muted p-1"> <div class="tile tile-muted p-1">

@ -4,24 +4,18 @@
<!-- Block Details --> <!-- Block Details -->
<div class="card card-mr-50-md"> <div class="card card-mr-50-md">
<div class="card-body"> <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)) %> <%= gettext("%{block_type} Details", block_type: block_type(@block)) %>
</h1> </h1>
<!-- Block Height --> <!-- Block Height -->
<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.">
<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.">
<%= if block_type(@block) == "Block" do %> <%= if block_type(@block) == "Block" do %>
<%= gettext("Block Height: %{height}", height: @block.number) %> <%= if @block.number == 0, do: "- " <> gettext("Genesis Block")%> <%= gettext("Block Height: %{height}", height: @block.number) %> <%= if @block.number == 0, do: "- " <> gettext("Genesis Block")%>
<% else %> <% else %>
<%= gettext("%{block_type} Height:", block_type: block_type(@block)) %> <%= gettext("%{block_type} Height:", block_type: block_type(@block)) %>
<%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %> <%= link(@block, to: block_path(BlockScoutWeb.Endpoint, :show, @block.number)) %>
<% end %> <% end %>
</h3> </h3>
<div class="d-flex justify-content-start text-muted block-details-row"> <div class="d-flex justify-content-start text-muted block-details-row">
<!-- # of Transactions --> <!-- # of Transactions -->
<span class="mr-4 block-detail-el"> <%= gettext "%{count} Transactions", count: @block_transaction_count %> </span> <span class="mr-4 block-detail-el"> <%= gettext "%{count} Transactions", count: @block_transaction_count %> </span>
@ -36,19 +30,14 @@
</div> </div>
<hr> <hr>
<!-- Block details description list --> <!-- Block details description list -->
<!-- Block Hash --> <!-- Block Hash -->
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Hash" %></dt> <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> <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> </dl>
<%= unless @block.number == 0 do %> <%= unless @block.number == 0 do %>
<!-- Parent Hash --> <!-- Parent Hash -->
<dl class="row"> <dl class="row">
@ -68,7 +57,7 @@
<!-- Difficulty value --> <!-- Difficulty value -->
<dl class="row"> <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Difficulty" %></dt> <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> <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> </dd>
</dl> </dl>
@ -95,6 +84,9 @@
<%= link( <%= link(
gettext("Position %{index}", index: index), gettext("Position %{index}", index: index),
class: "transaction__link", class: "transaction__link",
"data-toggle": "tooltip",
"data-placement": "top" ,
"data-original-title": "Index position(s) of referenced stale blocks." ,
"data-test": "uncle_link", "data-test": "uncle_link",
"data-uncle-hash": to_string(relation.uncle_hash), "data-uncle-hash": to_string(relation.uncle_hash),
to: block_path(@conn, :show, relation.uncle_hash) to: block_path(@conn, :show, relation.uncle_hash)
@ -106,11 +98,11 @@
<!-- Otherwise it will be displayed in its own block --> <!-- Otherwise it will be displayed in its own block -->
<%= if show_reward?(@block.rewards) do %> <%= if show_reward?(@block.rewards) do %>
<dl class="row" > <dl class="row">
<dt class="col-sm-3 text-muted"><%= gettext "Gas Used" %></dt> <dt class="col-sm-3 text-muted"><%= gettext "Gas Used" %></dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<span><%= @block.gas_used |> BlockScoutWeb.Cldr.Number.to_string! %></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 / limit).">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</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> </dt>
</dl> </dl>
<dl class="row mb-0"> <dl class="row mb-0">
@ -148,17 +140,13 @@
</div> </div>
<!-- Validator Reward or Gas--> <!-- 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"> <div class="card-body card-body-flex-column-space-between">
<%= if show_reward?(@block.rewards) do %> <%= if show_reward?(@block.rewards) do %>
<h2 class="card-title balance-card-title"><%= gettext "Block Rewards" %></h2> <h2 class="card-title balance-card-title"><%= gettext "Block Rewards" %></h2>
<div class="text-right tooltipCustom"> <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.">
<span class="tooltiptextTopR"> Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees. </span>
<%= for block_reward <- @block.rewards do %> <%= 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 %> <% end %>
</div> </div>
<% else %> <% 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> <span class="text-muted">(<%= (Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit)) |> BlockScoutWeb.Cldr.Number.to_string!(format: "#.#%") %>)</span>
</h3> </h3>
<p class="address-current-balance"><%= @block.gas_limit |> BlockScoutWeb.Cldr.Number.to_string! %><%= gettext "Gas Limit" %></p> <p class="address-current-balance"><%= @block.gas_limit |> BlockScoutWeb.Cldr.Number.to_string! %><%= gettext "Gas Limit" %></p>
</div> </div>
<% end %> <% end %>
</div> </div>

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

@ -70,3 +70,51 @@
</div> </div>
</div> </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>

@ -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 %> <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div> </div>
</div> </div>
</section> </section>

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

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

@ -3,6 +3,15 @@ defmodule BlockScoutWeb.ChainView do
alias BlockScoutWeb.LayoutView 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 defp market_cap(:standard, exchange_rate) do
exchange_rate.market_cap_usd exchange_rate.market_cap_usd
end end

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

@ -35,7 +35,7 @@ defmodule BlockScoutWeb.WeiHelpers do
"10,000 Gwei" "10,000 Gwei"
iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 21)}, :ether) iex> format_wei_value(%Wei{value: Decimal.new(1, 10, 21)}, :ether)
"10,000 POA" "10,000 Ether"
# With formatting options # With formatting options
@ -43,7 +43,7 @@ defmodule BlockScoutWeb.WeiHelpers do
...> %Wei{value: Decimal.new(1000500000000000000)}, ...> %Wei{value: Decimal.new(1000500000000000000)},
...> :ether ...> :ether
...> ) ...> )
"1.0005 POA" "1.0005 Ether"
iex> format_wei_value( iex> format_wei_value(
...> %Wei{value: Decimal.new(10)}, ...> %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 defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
use BlockScoutWeb.ConnCase use BlockScoutWeb.ConnCase
alias Explorer.Factory alias Explorer.{Chain, Factory}
describe "listcontracts" do describe "listcontracts" do
setup do setup do
@ -450,7 +450,9 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
expected_result = [ expected_result = [
%{ %{
"Address" => to_string(contract.address_hash), "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), "ABI" => Jason.encode!(contract.abi),
"ContractName" => contract.name, "ContractName" => contract.name,
"CompilerVersion" => contract.compiler_version, "CompilerVersion" => contract.compiler_version,
@ -496,9 +498,13 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
|> get("/api", params) |> get("/api", params)
|> json_response(200) |> json_response(200)
verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
expected_result = %{ expected_result = %{
"Address" => to_string(contract_address.hash), "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), "ABI" => Jason.encode!(contract_code_info.abi),
"ContractName" => contract_code_info.name, "ContractName" => contract_code_info.name,
"CompilerVersion" => contract_code_info.version, "CompilerVersion" => contract_code_info.version,
@ -563,8 +569,14 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
result = response["result"] result = response["result"]
verified_contract = Chain.address_hash_to_smart_contract(contract_address.hash)
assert result["Address"] == to_string(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["ContractName"] == name
assert result["DecompiledSourceCode"] == "Contract source code not decompiled." assert result["DecompiledSourceCode"] == "Contract source code not decompiled."
assert result["DecompilerVersion"] == "" assert result["DecompilerVersion"] == ""

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

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

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

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

@ -8,13 +8,13 @@ defmodule BlockScoutWeb.AddressCoinBalanceViewTest do
test "format the wei value in ether" do test "format the wei value in ether" do
wei = Wei.from(Decimal.new(1_340_000_000), :gwei) 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
test "format negative values" do test "format negative values" do
wei = Wei.from(Decimal.new(-1_340_000_000), :gwei) 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
end end
@ -50,13 +50,13 @@ defmodule BlockScoutWeb.AddressCoinBalanceViewTest do
test "format positive values" do test "format positive values" do
value = Decimal.new(1_340_000_000_000_000_000) 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
test "format negative values" do test "format negative values" do
value = Decimal.new(-1_340_000_000_000_000_000) 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 end
end end

@ -38,22 +38,25 @@ defmodule BlockScoutWeb.AddressContractViewTest do
result = AddressContractView.contract_lines_with_index(code) result = AddressContractView.contract_lines_with_index(code)
assert result == [ assert result == [
{"pragma solidity >=0.4.22 <0.6.0;", " 1"}, {"/**", " 1"},
{"", " 2"}, {"* Submitted for verification at blockscout.com on ", " 2"},
{"struct Proposal {", " 3"}, {"*/", " 3"},
{" uint voteCount;", " 4"}, {"pragma solidity >=0.4.22 <0.6.0;", " 4"},
{"}", " 5"}, {"", " 5"},
{"", " 6"}, {"struct Proposal {", " 6"},
{"address chairperson;", " 7"}, {" uint voteCount;", " 7"},
{"mapping(address => Voter) voters;", " 8"}, {"}", " 8"},
{"Proposal[] proposals;", " 9"}, {"", " 9"},
{"", "10"}, {"address chairperson;", "10"},
{"constructor(uint8 _numProposals) public {", "11"}, {"mapping(address => Voter) voters;", "11"},
{" chairperson = msg.sender;", "12"}, {"Proposal[] proposals;", "12"},
{" voters[chairperson].weight = 1;", "13"}, {"", "13"},
{" proposals.length = _numProposals;", "14"}, {"constructor(uint8 _numProposals) public {", "14"},
{"}", "15"}, {" chairperson = msg.sender;", "15"},
{"", "16"} {" voters[chairperson].weight = 1;", "16"},
{" proposals.length = _numProposals;", "17"},
{"}", "18"},
{"", "19"}
] ]
end 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 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") 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 end
end end

@ -3,32 +3,6 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
alias BlockScoutWeb.APIDocsView 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 describe "api_url/1" do
setup do setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint) 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" assert APIDocsView.api_url() == "https://blockscout.com/api"
end 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 end
describe "eth_rpc_api_url/1" do 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" assert APIDocsView.eth_rpc_api_url() == "https://blockscout.com/api/eth_rpc"
end 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
end end

@ -91,7 +91,7 @@ defmodule BlockScoutWeb.BlockViewTest do
block = Repo.preload(block, :rewards) 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 end
end end

@ -136,7 +136,7 @@ defmodule BlockScoutWeb.TransactionViewTest do
gas_used: nil 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) assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
end end
@ -144,12 +144,9 @@ defmodule BlockScoutWeb.TransactionViewTest do
{:ok, gas_price} = Wei.cast(3_000_000_000) {: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)) 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) assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
end end
test "with fee but no available exchange_rate" do
end
end end
describe "formatted_status/1" do describe "formatted_status/1" do

@ -27,12 +27,18 @@ config :explorer, Explorer.Counters.AverageBlockTime,
enabled: true, enabled: true,
period: average_block_period 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, config :explorer, Explorer.ExchangeRates.Source.CoinGecko, coin_id: System.get_env("COIN_GECKO_ID", "poa-network")
pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10")
balances_update_interval = balances_update_interval =
if System.get_env("ADDRESS_WITH_BALANCES_UPDATE_INTERVAL") do 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.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 # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

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

@ -230,9 +230,8 @@ defmodule Explorer.Chain do
Reward.fetch_emission_rewards_tuples(address_hash, paging_options) Reward.fetch_emission_rewards_tuples(address_hash, paging_options)
end) end)
address_hash [rewards_task | address_to_transactions_tasks(address_hash, options)]
|> address_to_transactions_without_rewards(paging_options, options) |> wait_for_address_transactions()
|> Enum.concat(Task.await(rewards_task, :timer.seconds(20)))
|> Enum.sort_by(fn item -> |> Enum.sort_by(fn item ->
case item do case item do
{%Reward{} = emission_reward, _} -> {%Reward{} = emission_reward, _} ->
@ -242,32 +241,83 @@ defmodule Explorer.Chain do
{-item.block_number, -item.index} {-item.block_number, -item.index}
end end
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) |> Enum.take(paging_options.page_size)
else else
address_to_transactions_without_rewards(address_hash, paging_options, options) address_to_transactions_without_rewards(address_hash, options)
end end
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) direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
transaction_hashes_from_token_transfers = base_query =
TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options)
paging_options paging_options
|> fetch_transactions() |> fetch_transactions()
|> Transaction.where_transaction_matches(transaction_hashes_from_token_transfers, direction, address_hash)
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)
|> Transaction.preload_token_transfers(address_hash) |> Transaction.preload_token_transfers(address_hash)
|> Repo.all()
direction_tasks =
base_query
|> Transaction.matching_address_queries_list(direction, address_hash)
|> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end)
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 end
@spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()] @spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()]
def address_to_logs(address_hash, options \\ []) when is_list(options) do def address_to_logs(address_hash, options \\ []) when is_list(options) do
paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} 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 = base_query =
from(log in Log, from(log in Log,
@ -1176,7 +1226,7 @@ defmodule Explorer.Chain do
""" """
@spec indexed_ratio() :: Decimal.t() @spec indexed_ratio() :: Decimal.t()
def indexed_ratio do def indexed_ratio do
{min, max} = BlockNumber.min_and_max_numbers() %{min: min, max: max} = BlockNumber.get_all()
case {min, max} do case {min, max} do
{0, 0} -> {0, 0} ->
@ -1189,20 +1239,30 @@ defmodule Explorer.Chain do
end end
end end
@spec fetch_min_and_max_block_numbers() :: {non_neg_integer, non_neg_integer} @spec fetch_min_block_number() :: non_neg_integer
def fetch_min_and_max_block_numbers do def fetch_min_block_number do
query = query =
from(block in Block, from(block in Block,
select: {min(block.number), max(block.number)}, select: block.number,
where: block.consensus == true where: block.consensus == true,
order_by: [asc: block.number],
limit: 1
) )
result = Repo.one!(query) Repo.one(query) || 0
case result do
{nil, nil} -> {0, 0}
_ -> result
end 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 end
@spec fetch_count_consensus_block() :: non_neg_integer @spec fetch_count_consensus_block() :: non_neg_integer
@ -2195,7 +2255,7 @@ defmodule Explorer.Chain do
""" """
@spec transaction_estimated_count() :: non_neg_integer() @spec transaction_estimated_count() :: non_neg_integer()
def transaction_estimated_count do def transaction_estimated_count do
cached_value = TransactionCount.value() cached_value = TransactionCount.get_count()
if is_nil(cached_value) do if is_nil(cached_value) do
%Postgrex.Result{rows: [[rows]]} = %Postgrex.Result{rows: [[rows]]} =
@ -2214,7 +2274,7 @@ defmodule Explorer.Chain do
""" """
@spec block_estimated_count() :: non_neg_integer() @spec block_estimated_count() :: non_neg_integer()
def block_estimated_count do def block_estimated_count do
cached_value = BlockCount.count() cached_value = BlockCount.get_count()
if is_nil(cached_value) do if is_nil(cached_value) do
%Postgrex.Result{rows: [[count]]} = Repo.query!("SELECT reltuples FROM pg_class WHERE relname = 'blocks';") %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 @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 def address_hash_to_address_with_source_code(address_hash) do
case Repo.get(Address, address_hash) do case Repo.get(Address, address_hash) do
nil -> nil nil ->
address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts]) 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
end end
@ -2717,7 +2795,7 @@ defmodule Explorer.Chain do
end end
defp supply_module do defp supply_module do
Application.get_env(:explorer, :supply, Explorer.Chain.Supply.CoinMarketCap) Application.get_env(:explorer, :supply, Explorer.Chain.Supply.ExchangeRate)
end end
@doc """ @doc """

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

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

@ -5,143 +5,61 @@ defmodule Explorer.Chain.Cache.BlockCount do
require Logger require Logger
use GenServer @default_cache_period :timer.minutes(10)
alias Explorer.Chain use Explorer.Chain.MapCache,
name: :block_count,
# 10 minutes key: :count,
@cache_period 1_000 * 60 * 10 key: :async_task,
@key "count" global_ttl: cache_period(),
@default_value nil ttl_check_interval: :timer.minutes(1),
@name __MODULE__ callback: &async_task_on_deletion(&1)
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
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 alias Explorer.Chain
{:noreply, {{cache_period, default_value, name}, nil}}
end
def handle_info(_, {{cache_period, default_value, name}, _}) do defp handle_fallback(:count) do
{:noreply, {{cache_period, default_value, name}, nil}} # This will get the task PID if one exists and launch a new task if not
end # See next `handle_fallback` definition
get_async_task()
# sobelow_skip ["DOS"] {:return, nil}
defp table_name(name) do
name
|> Atom.to_string()
|> Macro.underscore()
|> String.to_atom()
end end
def async_update_cache(name) do defp handle_fallback(:async_task) do
Task.async(fn -> # 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 try do
result = Chain.fetch_count_consensus_block() result = Chain.fetch_count_consensus_block()
GenServer.cast(name, {:update_cache, result}) set_count(result)
rescue rescue
e -> e ->
Logger.debug([ Logger.debug([
"Coudn't update block count test #{inspect(e)}" "Coudn't update block count test #{inspect(e)}"
]) ])
end end
end)
end
defp init_ets_table(name) do set_async_task(nil)
table_name = table_name(name) end)
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)
case :ets.lookup(table_name, @key) do {:update, task}
[{_, cached_values}] -> cached_values
_ -> nil
end
end end
defp current_time do # By setting this as a `callback` an async task will be started each time the
utc_now = DateTime.utc_now() # `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) defp async_task_on_deletion(_data), do: nil
end
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 cache_period do
nil "BLOCK_COUNT_CACHE_PERIOD"
|> System.get_env("")
|> Integer.parse()
|> case do
{integer, ""} -> :timer.seconds(integer)
_ -> @default_cache_period
end end
end end
end end

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

@ -11,7 +11,9 @@ defmodule Explorer.Chain.Cache.Blocks do
ids_list_key: "block_numbers", ids_list_key: "block_numbers",
preload: :transactions, preload: :transactions,
preload: [miner: :names], 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() @type element :: Block.t()

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

@ -3,152 +3,64 @@ defmodule Explorer.Chain.Cache.TransactionCount do
Cache for estimated transaction count. 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.Chain.Transaction
alias Explorer.Repo alias Explorer.Repo
# 2 hours defp handle_fallback(:count) do
@cache_period 1_000 * 60 * 60 * 2 # This will get the task PID if one exists and launch a new task if not
@default_value nil # See next `handle_fallback` definition
@key "count" get_async_task()
@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)}
{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 end
def update_cache(nil, name) do defp handle_fallback(:async_task) do
async_update_cache(name) # If this gets called it means an async task was requested, but none exists
end # so a new one needs to be launched
{:ok, task} =
def update_cache(task, _) do Task.start(fn ->
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})
{: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 try do
result = Repo.aggregate(Transaction, :count, :hash, timeout: :infinity) result = Repo.aggregate(Transaction, :count, :hash, timeout: :infinity)
GenServer.cast(name, {:update_cache, result}) set_count(result)
rescue rescue
e -> e ->
Logger.debug([ Logger.debug([
"Coudn't update transaction count test #{inspect(e)}" "Coudn't update transaction count test #{inspect(e)}"
]) ])
end end
end)
end
defp init_ets_table(name) do
table_name = table_name(name)
if :ets.whereis(table_name) == :undefined do set_async_task(nil)
:ets.new(table_name, [ end)
:set,
:named_table,
:public,
write_concurrency: true
])
end
end
defp cached_values(name) do
table_name = table_name(name)
case :ets.lookup(table_name, @key) do
[{_, cached_values}] -> cached_values
_ -> nil
end
end
defp schedule_cache_update do {:update, task}
Process.send_after(self(), :update_cache, 2_000)
end end
defp current_time do # By setting this as a `callback` an async task will be started each time the
utc_now = DateTime.utc_now() # `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) defp async_task_on_deletion(_data), do: nil
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 cache_period do
nil "TXS_COUNT_CACHE_PERIOD"
|> System.get_env("")
|> Integer.parse()
|> case do
{integer, ""} -> :timer.seconds(integer)
_ -> @default_cache_period
end end
end end
end end

@ -16,7 +16,9 @@ defmodule Explorer.Chain.Cache.Transactions do
token_transfers: :token, token_transfers: :token,
token_transfers: :from_address, token_transfers: :from_address,
token_transfers: :to_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() @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] preload: [transaction: :hash]
``` ```
Additionally all of the options accepted by `ConCache.start_link/1` can be 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` It's also possible, and advised, to override the implementation of the `c:prevails?/2`
and `c:element_to_id/1` callbacks. and `c:element_to_id/1` callbacks.
@ -131,10 +134,7 @@ defmodule Explorer.Chain.OrderedCache do
max_size = Keyword.get(opts, :max_size, 100) max_size = Keyword.get(opts, :max_size, 100)
preloads = Keyword.get(opts, :preloads) || Keyword.get_values(opts, :preload) preloads = Keyword.get(opts, :preloads) || Keyword.get_values(opts, :preload)
concache_params = concache_params = Keyword.drop(opts, [:ids_list_key, :max_size, :preloads, :preload])
opts
|> Keyword.drop([:ids_list_key, :max_size, :preloads, :preload])
|> Keyword.put_new(:ttl_check_interval, false)
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks # credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do quote do
@ -206,6 +206,19 @@ defmodule Explorer.Chain.OrderedCache do
### Updating function ### 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 @impl OrderedCache
def update(elements) when is_nil(elements), do: :ok def update(elements) when is_nil(elements), do: :ok
@ -217,7 +230,8 @@ defmodule Explorer.Chain.OrderedCache do
|> Enum.sort(&prevails?(&1, &2)) |> Enum.sort(&prevails?(&1, &2))
|> merge_and_update(ids || [], max_size()) |> 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)
end end
@ -308,7 +322,21 @@ defmodule Explorer.Chain.OrderedCache do
provided to this function will override the ones set by using the macro provided to this function will override the ones set by using the macro
""" """
def child_spec(params) do 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()) Supervisor.child_spec({ConCache, params}, id: child_id())
end end

@ -271,6 +271,45 @@ defmodule Explorer.Chain.SmartContract do
|> add_error(:contract_source_code, error_message(error)) |> add_error(:contract_source_code, error_message(error))
end 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 defp upsert_contract_methods(%Ecto.Changeset{changes: %{abi: abi}} = changeset) do
ContractMethod.upsert_from_abi(abi, get_field(changeset, :address_hash)) 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 """ @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 use Explorer.Chain.Supply

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

@ -484,40 +484,26 @@ defmodule Explorer.Chain.Transaction do
end end
@doc """ @doc """
Modifies a query to filter for transactions whose hash is in a list or that are Produces a list of queries starting from the given one and adding filters for
linked to the given address_hash through a direction. transactions 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.
""" """
def where_transaction_matches(query, transaction_hashes, :from, address_hash) do def matching_address_queries_list(query, :from, address_hash) do
where( [where(query, [t], t.from_address_hash == ^address_hash)]
query,
[t],
t.hash in ^transaction_hashes or
t.from_address_hash == ^address_hash
)
end end
def where_transaction_matches(query, transaction_hashes, :to, address_hash) do def matching_address_queries_list(query, :to, address_hash) do
where( [
query, where(query, [t], t.to_address_hash == ^address_hash),
[t], where(query, [t], t.created_contract_address_hash == ^address_hash)
t.hash in ^transaction_hashes or ]
t.to_address_hash == ^address_hash or
t.created_contract_address_hash == ^address_hash
)
end end
def where_transaction_matches(query, transaction_hashes, _direction, address_hash) do def matching_address_queries_list(query, _direction, address_hash) do
where( [
query, where(query, [t], t.from_address_hash == ^address_hash),
[t], where(query, [t], t.to_address_hash == ^address_hash),
t.hash in ^transaction_hashes or where(query, [t], t.created_contract_address_hash == ^address_hash)
t.from_address_hash == ^address_hash or ]
t.to_address_hash == ^address_hash or
t.created_contract_address_hash == ^address_hash
)
end end
@collated_fields ~w(block_number cumulative_gas_used gas_used index)a @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. Imports data from parity chain spec.
""" """
require Logger
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.{EmissionReward, Range} alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Hash.Address, as: AddressHash alias Explorer.Chain.Hash.Address, as: AddressHash
alias Explorer.Chain.Wei alias Explorer.Chain.Wei
alias Explorer.ChainSpec.GenesisData
alias Explorer.ChainSpec.POA.Importer, as: PoaEmissionImporter
@max_block_number :infinity @max_block_number :infinity
def import_emission_rewards(chain_spec) do def import_emission_rewards(chain_spec) do
rewards = emission_rewards(chain_spec) if Application.get_env(:explorer, GenesisData)[:emission_format] == "POA" do
PoaEmissionImporter.import_emission_rewards()
{_, nil} = Repo.delete_all(EmissionReward) else
{_, nil} = Repo.insert_all(EmissionReward, rewards) import_rewards_from_chain_spec(chain_spec)
end
end end
def import_genesis_coin_balances(chain_spec) do def import_genesis_coin_balances(chain_spec) do
@ -38,18 +43,37 @@ defmodule Explorer.ChainSpec.Parity.Importer do
Chain.import(params) Chain.import(params)
end 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 def genesis_coin_balances(chain_spec) do
accounts = chain_spec["accounts"] accounts = chain_spec["accounts"]
if accounts do
parse_accounts(accounts) parse_accounts(accounts)
else
Logger.warn(fn -> "No accounts are defined in chain spec" end)
[]
end
end end
def emission_rewards(chain_spec) do def emission_rewards(chain_spec) do
rewards = chain_spec["engine"]["Ethash"]["params"]["blockReward"] rewards = chain_spec["engine"]["Ethash"]["params"]["blockReward"]
if rewards do
rewards rewards
|> parse_hex_numbers() |> parse_hex_numbers()
|> format_ranges() |> format_ranges()
else
Logger.warn(fn -> "No rewards are defined in chain spec" end)
[]
end
end end
defp parse_accounts(accounts) do defp parse_accounts(accounts) do
@ -59,7 +83,7 @@ defmodule Explorer.ChainSpec.Parity.Importer do
end) end)
|> Stream.map(fn {address, %{"balance" => value}} -> |> Stream.map(fn {address, %{"balance" => value}} ->
{:ok, address_hash} = AddressHash.cast(address) {:ok, address_hash} = AddressHash.cast(address)
balance = parse_hex_number(value) balance = parse_number(value)
%{address_hash: address_hash, value: balance} %{address_hash: address_hash, value: balance}
end) end)
@ -92,16 +116,22 @@ defmodule Explorer.ChainSpec.Parity.Importer do
defp parse_hex_numbers(rewards) do defp parse_hex_numbers(rewards) do
Enum.map(rewards, fn {hex_block_number, hex_reward} -> Enum.map(rewards, fn {hex_block_number, hex_reward} ->
block_number = parse_hex_number(hex_block_number) block_number = parse_number(hex_block_number)
{:ok, reward} = hex_reward |> parse_hex_number() |> Wei.cast() {:ok, reward} = hex_reward |> parse_number() |> Wei.cast()
{block_number, reward} {block_number, reward}
end) end)
end end
defp parse_hex_number("0x" <> hex_number) do defp parse_number("0x" <> hex_number) do
{number, ""} = Integer.parse(hex_number, 16) {number, ""} = Integer.parse(hex_number, 16)
number number
end end
defp parse_number(string_number) do
{number, ""} = Integer.parse(string_number, 10)
number
end
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 """ @moduledoc """
Behaviour for fetching exchange rates from external sources. Behaviour for fetching exchange rates from external sources.
""" """
alias Explorer.ExchangeRates.Source.CoinMarketCap
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias HTTPoison.{Error, Response} alias HTTPoison.{Error, Response}
@ -12,34 +10,18 @@ defmodule Explorer.ExchangeRates.Source do
""" """
@spec fetch_exchange_rates(module) :: {:ok, [Token.t()]} | {:error, any} @spec fetch_exchange_rates(module) :: {:ok, [Token.t()]} | {:error, any}
def fetch_exchange_rates(source \\ exchange_rates_source()) do 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) fetch_exchange_rates_request(source)
end 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
end
defp fetch_exchange_rates_request(source) do defp fetch_exchange_rates_request(source) do
case HTTPoison.get(source.source_url(), headers()) do case HTTPoison.get(source.source_url(), headers()) do
{:ok, %Response{body: body, status_code: 200}} -> {: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 -> {:ok, %Response{body: body, status_code: status_code}} when status_code in 400..499 ->
{:error, decode_json(body)["error"]} {:error, decode_json(body)["error"]}
@ -83,7 +65,7 @@ defmodule Explorer.ExchangeRates.Source do
@spec exchange_rates_source() :: module() @spec exchange_rates_source() :: module()
defp exchange_rates_source do defp exchange_rates_source do
config(:source) || Explorer.ExchangeRates.Source.CoinMarketCap config(:source) || Explorer.ExchangeRates.Source.CoinGecko
end end
@spec config(atom()) :: term @spec config(atom()) :: term

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

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

@ -11,49 +11,63 @@ defmodule Explorer.Chain.Cache.BlockNumberTest do
end) end)
end end
describe "max_number/1" do describe "get_max/0" do
test "returns max number" do test "returns max number" do
insert(:block, number: 5) insert(:block, number: 5)
BlockNumber.setup() assert BlockNumber.get_max() == 5
assert BlockNumber.max_number() == 5
end end
end end
describe "min_number/1" do describe "get_min/0" do
test "returns max number" do test "returns min number" do
insert(:block, number: 2) 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
end end
describe "update/1" do describe "update_all/1" do
test "updates max number" do test "updates max number" do
insert(:block, number: 2) insert(:block, number: 2)
BlockNumber.setup() assert BlockNumber.get_max() == 2
assert BlockNumber.max_number() == 2
assert BlockNumber.update(3) assert BlockNumber.update_all(3)
assert BlockNumber.max_number() == 3 assert BlockNumber.get_max() == 3
end end
test "updates min number" do test "updates min number" do
insert(:block, number: 2) 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 end
end end

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

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

@ -1121,23 +1121,31 @@ defmodule Explorer.ChainTest do
end end
end end
describe "fetch_min_and_max_block_numbers/0" do describe "fetch_min_block_number/0" do
test "fetches min and max block numbers" do test "fetches min block numbers" do
for index <- 5..9 do for index <- 5..9 do
insert(:block, number: index) insert(:block, number: index)
end end
assert {5, 9} = Chain.fetch_min_and_max_block_numbers() assert 5 = Chain.fetch_min_block_number()
end end
test "fetches min and max when there are no blocks" do test "fetches min when there are no blocks" do
assert {0, 0} = Chain.fetch_min_and_max_block_numbers() assert 0 = Chain.fetch_min_block_number()
end
end end
test "fetches min and max where there is only one block" do describe "fetch_max_block_number/0" do
insert(:block, number: 1) 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
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 describe "format_data/1" do
setup do setup do
bypass = Bypass.open() bypass = Bypass.open()
@ -59,31 +31,30 @@ defmodule Explorer.ExchangeRates.Source.CoinGeckoTest do
Conn.resp(conn, 200, @json_btc_price) Conn.resp(conn, 200, @json_btc_price)
end) 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 = [ expected = [
%Token{ %Token{
available_supply: Decimal.new("252193195"), available_supply: Decimal.new("220167621.0"),
total_supply: Decimal.new("252193195"), total_supply: Decimal.new("252193195.0"),
btc_value: Decimal.new("0.00001753101509231471092879666458"), btc_value: Decimal.new("0.000002055310963802830367634997491"),
id: "poa-network", id: "poa-network",
last_updated: expected_date, last_updated: ~U[2019-08-21 08:36:49.371Z],
market_cap_usd: Decimal.new("25248999.6735956"), market_cap_usd: Decimal.new("2962791"),
name: "POA Network", name: "POA Network",
symbol: "poa", symbol: "POA",
usd_value: Decimal.new("0.114782883773693"), usd_value: Decimal.new("0.01345698"),
volume_24h_usd: Decimal.new("2344442.13578437") volume_24h_usd: Decimal.new("119946")
} }
] ]
assert expected == CoinGecko.format_data(@json_mkt_data) assert expected == CoinGecko.format_data(json_data)
end end
test "returns nothing when given bad data", %{bypass: bypass} do test "returns nothing when given bad data" do
Bypass.expect(bypass, "GET", "/exchange_rates", fn conn ->
Conn.resp(conn, 200, @json_btc_price)
end)
bad_data = """ bad_data = """
[{"id": "poa-network"}] [{"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 defmodule Explorer.ExchangeRates.Source.TokenBridgeTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.ExchangeRates.Source.CoinGecko
alias Explorer.ExchangeRates.Source.TokenBridge alias Explorer.ExchangeRates.Source.TokenBridge
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Plug.Conn
@json "#{File.cwd!()}/test/support/fixture/exchange_rates/coin_gecko.json"
|> File.read!()
|> Jason.decode!()
@json """ @json_btc_price """
[
{ {
"id": "poa-network", "rates": {
"name": "POA Network", "usd": {
"symbol": "POA", "name": "US Dollar",
"rank": "103", "unit": "$",
"price_usd": "0.485053", "value": 6547.418,
"price_btc": "0.00007032", "type": "fiat"
"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 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) assert [%Token{}] = TokenBridge.format_data(@json)
end end
end end

@ -39,7 +39,8 @@ defmodule Explorer.DataCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end 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.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_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()) Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())

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

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

@ -14,54 +14,57 @@ $ export NETWORK=POA
``` ```
| Variable | Required | Description | Default | Version | Need recompile | | 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 | | | `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 | | | `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 | | | `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 | | | `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_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_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_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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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 | | | `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_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 | | | `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_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 | | | `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_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 | | | `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+ | | | `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+ | | | `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 | | | `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+ | | | `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: | | `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+ | | | `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+ | | | `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+ | | | `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+ | | | `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+ | | | `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+ | | | `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+ | | | `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+ | | | `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+ | | | `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+ | | `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+ | | `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_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_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_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: | | `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+ | | | `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+ | | | `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 | | | `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