Merge branch 'master' into reduce-transaction-status-constraint

pull/2198/head
Victor Baranov 5 years ago committed by GitHub
commit cdf08694ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      CHANGELOG.md
  2. 277
      README.md
  3. 20
      UPGRADING.md
  4. 4
      apps/block_scout_web/assets/css/app.scss
  5. 34
      apps/block_scout_web/assets/css/components/_navbar.scss
  6. 334
      apps/block_scout_web/assets/css/components/_network-selector.scss
  7. 49
      apps/block_scout_web/assets/css/components/_radio.scss
  8. 49
      apps/block_scout_web/assets/css/components/_transaction.scss
  9. 4
      apps/block_scout_web/assets/js/app.js
  10. 8
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  11. 77
      apps/block_scout_web/assets/js/lib/network_selector.js
  12. 9
      apps/block_scout_web/assets/js/pages/address/logs.js
  13. 58
      apps/block_scout_web/assets/js/pages/favorites.js
  14. 9
      apps/block_scout_web/assets/js/pages/layout.js
  15. 21
      apps/block_scout_web/assets/js/pages/network-search.js
  16. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/aerum-mainnet.png
  17. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.png
  18. 7
      apps/block_scout_web/assets/static/images/network-selector-icons/callisto.svg
  19. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/core.svg
  20. 33
      apps/block_scout_web/assets/static/images/network-selector-icons/dai.svg
  21. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.png
  22. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-mainnet.png
  23. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.png
  24. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/kovan-testnet.png
  25. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.png
  26. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/poa-sokol.png
  27. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby-testnet.png
  28. 15
      apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby.svg
  29. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.png
  30. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/ropsten.svg
  31. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.png
  32. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/sokol.svg
  33. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.png
  34. 2
      apps/block_scout_web/config/config.exs
  35. 41
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  36. 16
      apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex
  37. 11
      apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
  38. 2
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  39. 4
      apps/block_scout_web/lib/block_scout_web/router.ex
  40. 4
      apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex
  41. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  42. 12
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  43. 15
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  44. 4
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex
  45. 2
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/index.html.eex
  46. 2
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  47. 53
      apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex
  48. 25
      apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex
  49. 21
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  50. 9
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
  51. 12
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  52. 10
      apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
  53. 8
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  54. 10
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  55. 15
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  56. 139
      apps/block_scout_web/priv/gettext/default.pot
  57. 143
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  58. 6
      apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs
  59. 36
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  60. 15
      apps/explorer/config/config.exs
  61. 4
      apps/explorer/lib/explorer/application.ex
  62. 36
      apps/explorer/lib/explorer/chain.ex
  63. 119
      apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
  64. 139
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  65. 4
      apps/explorer/lib/explorer/chain/block.ex
  66. 103
      apps/explorer/lib/explorer/chain/supply/rsk.ex
  67. 14
      apps/explorer/lib/explorer/counters/average_block_time.ex
  68. 19
      apps/explorer/lib/explorer/market/market.ex
  69. 79
      apps/explorer/lib/explorer/market/market_history_cache.ex
  70. 10
      apps/explorer/lib/explorer/smart_contract/solidity/code_compiler.ex
  71. 7
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  72. 8
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  73. 2
      apps/explorer/mix.exs
  74. 5
      apps/explorer/priv/compile_solc.js
  75. 72
      apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs
  76. 105
      apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs
  77. 139
      apps/explorer/test/explorer/chain/supply/rsk_test.exs
  78. 4
      apps/explorer/test/explorer/chain_test.exs
  79. 31
      apps/explorer/test/explorer/counters/average_block_time_test.exs
  80. 90
      apps/explorer/test/explorer/market/market_history_cache_test.exs
  81. 27
      apps/explorer/test/explorer/market/market_test.exs
  82. 14
      apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
  83. 32
      apps/explorer/test/explorer/smart_contract/verifier_test.exs
  84. 4
      apps/explorer/test/support/data_case.ex
  85. 3874
      apps/explorer/test/support/fixture/smart_contract/large_smart_contract.sol
  86. 76
      apps/explorer/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol
  87. 3
      apps/indexer/config/config.exs
  88. 5
      apps/indexer/lib/indexer/fetcher/token_balance.ex
  89. 2
      apps/indexer/lib/indexer/fetcher/token_updater.ex
  90. 2
      apps/indexer/lib/indexer/supervisor.ex
  91. 2
      apps/indexer/mix.exs
  92. 35
      apps/indexer/test/indexer/fetcher/token_balance_test.exs
  93. 2
      bin/install_chrome_headless.sh
  94. 2
      docs/_sidebar.md
  95. 4
      docs/about.md
  96. 2
      docs/ansible-deployment.md
  97. 2
      docs/env-variables.md
  98. 2
      docs/faqs.md
  99. 9
      docs/index.html
  100. 20
      docs/projects.md
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,12 +1,23 @@
## Current
### Features
- [#2182](https://github.com/poanetwork/blockscout/pull/2182) - add market history cache
- [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch
- [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache
- [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty
- [#2191](https://github.com/poanetwork/blockscout/pull/2191) - allow to configure token metadata update interval
- [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint
- [#2190](https://github.com/poanetwork/blockscout/pull/2190) - show all token transfers
- [#2193](https://github.com/poanetwork/blockscout/pull/2193) - feat: add BLOCKSCOUT_HOST, and use it in API docs
- [#2266](https://github.com/poanetwork/blockscout/pull/2266) - allow excluding uncles from average block time calculation
### Fixes
- [#2263](https://github.com/poanetwork/blockscout/pull/2263) - added an ability to close network selector on outside click
- [#2257](https://github.com/poanetwork/blockscout/pull/2257) - 'download csv' button added to different tabs
- [#2242](https://github.com/poanetwork/blockscout/pull/2242) - added styles for 'download csv' button
- [#2261](https://github.com/poanetwork/blockscout/pull/2261) - header logo aligned to the center properly
- [#2254](https://github.com/poanetwork/blockscout/pull/2254) - search length issue, tile link wrapping issue
- [#2238](https://github.com/poanetwork/blockscout/pull/2238) - header content alignment issue, hide navbar on outside click
- [#2229](https://github.com/poanetwork/blockscout/pull/2229) - gap issue between qr and copy button in token transfers, top cards width and height issue
- [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix
- [#2179](https://github.com/poanetwork/blockscout/pull/2179) - fix docker build error
- [#2165](https://github.com/poanetwork/blockscout/pull/2165) - sort blocks by timestamp when calculating average block time
@ -42,19 +53,26 @@
- [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions
- [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining
- [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test
- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints
- [#2198](https://github.com/poanetwork/blockscout/pull/2198) - reduce transaction status constraint
- [#2198](https://github.com/poanetwork/blockscout/pull/2198) - reduce transaction status and error constraint
- [#2167](https://github.com/poanetwork/blockscout/pull/2167) - feat: document eth rpc api mimicking endpoints
- [#2225](https://github.com/poanetwork/blockscout/pull/2225) - fix metadata decoding in Solidity 0.5.9 smart contract verification
- [#2204](https://github.com/poanetwork/blockscout/pull/2204) - fix large contract verification
- [#2247](https://github.com/poanetwork/blockscout/pull/2247) - hide logs search if there are no logs
- [#2248](https://github.com/poanetwork/blockscout/pull/2248) - sort block after query execution for average block time
- [#2270](https://github.com/poanetwork/blockscout/pull/2270) - Remove duplicate params in `Indexer.Fetcher.TokenBalance`
- [#2268](https://github.com/poanetwork/blockscout/pull/2268) - remove not existing assigns in html code
### Chore
- [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version
- [#2118](https://github.com/poanetwork/blockscout/pull/2118) - show only the last decompiled contract
- [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver
### Chore
## 2.0.0-beta
### Features
- [#2044](https://github.com/poanetwork/blockscout/pull/2044) - New network selector.
- [#2091](https://github.com/poanetwork/blockscout/pull/2091) - Added "Question" modal.
- [#1963](https://github.com/poanetwork/blockscout/pull/1963), [#1959](https://github.com/poanetwork/blockscout/pull/1959), [#1948](https://github.com/poanetwork/blockscout/pull/1948), [#1936](https://github.com/poanetwork/blockscout/pull/1936), [#1925](https://github.com/poanetwork/blockscout/pull/1925), [#1922](https://github.com/poanetwork/blockscout/pull/1922), [#1903](https://github.com/poanetwork/blockscout/pull/1903), [#1874](https://github.com/poanetwork/blockscout/pull/1874), [#1895](https://github.com/poanetwork/blockscout/pull/1895), [#2031](https://github.com/poanetwork/blockscout/pull/2031), [#2073](https://github.com/poanetwork/blockscout/pull/2073), [#2074](https://github.com/poanetwork/blockscout/pull/2074), - added new themes and logos for poa, eth, rinkeby, goerli, ropsten, kovan, sokol, xdai, etc, rsk and default theme
- [#1726](https://github.com/poanetwork/blockscout/pull/2071) - Updated styles for the new smart contract page.
@ -80,6 +98,9 @@
- [#2100](https://github.com/poanetwork/blockscout/pull/2100) - feat: eth_get_balance rpc endpoint
### Fixes
- [#2228](https://github.com/poanetwork/blockscout/pull/2228) - favorites duplication issues, active radio issue
- [#2207](https://github.com/poanetwork/blockscout/pull/2207) - new 'download csv' button design
- [#2206](https://github.com/poanetwork/blockscout/pull/2206) - added styles for 'Download All Transactions as CSV' button
- [#2099](https://github.com/poanetwork/blockscout/pull/2099) - logs search input width
- [#2098](https://github.com/poanetwork/blockscout/pull/2098) - nav dropdown issue, logo size issue
- [#2082](https://github.com/poanetwork/blockscout/pull/2082) - dropdown styles, tooltip gap fix, 404 page added

@ -14,9 +14,9 @@
BlockScout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on **all EVM** (Ethereum Virtual Machine) blockchains. This includes the Ethereum main and test networks as well as **Ethereum forks and sidechains**.
Following is an overview of the project and instructions for [getting started](#getting-started).
See our [project documentation](https://poanetwork.github.io/blockscout) for detailed information and setup instructions.
Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for additional deployment instructions, FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here.
Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here.
You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout).
@ -24,24 +24,9 @@ You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/p
BlockScout is an Elixir application that allows users to search transactions, view accounts and balances, and verify smart contracts on the entire Ethereum network including all forks and sidechains.
Currently available block explorers (i.e. Etherscan and Etherchain) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent tools are needed to analyze and validate transactions.
Currently available full-featured block explorers (Etherscan, Etherchain, Blockchair) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent, open-source tools are needed to analyze and validate transactions.
### Features
- [x] **Open source development**: The code is community driven and available for anyone to use, explore and improve.
- [x] **Real time transaction tracking**: Transactions are updated in real time - no page refresh required. Infinite scrolling is also enabled.
- [x] **Smart contract interaction**: Users can read and verify Solidity smart contracts and access pre-existing contracts to fast-track development. Support for Vyper, LLL, and Web Assembly contracts is in progress.
- [x] **Token support**: ERC20 and ERC721 tokens are supported. Future releases will support additional token types including ERC223 and ERC1155.
- [x] **User customization**: Users can easily deploy on a network and customize the Bootstrap interface.
- [x] **Ethereum sidechain networks**: BlockScout supports the Ethereum mainnet, Ethereum testnets, POA network, and forks like Ethereum Classic, xDAI, additional sidechains, and private EVM networks.
### Supported Projects
## Supported Projects
| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** |
|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------|
@ -58,256 +43,17 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
| | | [Tenda](https://tenda.network) |
### Visual Interface
Interface for the POA network _updated 02/2019_
![BlockScout Example](explorer_example_2_2019.gif)
### Umbrella Project Organization
This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html). Each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`.
Each OTP application has a restricted domain.
| Directory | OTP Application | Namespace | Purpose |
|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` |
| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. |
| `apps/block_scout_web` | `:block_scout_web` | `BlockScoutWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:block_scout_web`. Any business rules or interface not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. |
| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`. This restricts automatic writes to `:indexer` and read-only mode can be achieved by not running `:indexer`. |
Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).
## Getting Started
### Requirements
| Dependency | Mac | Linux |
|-------------|-----|-------|
| [Erlang/OTP 21.0.4](https://github.com/erlang/otp) | `brew install erlang` | [Erlang Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L134) |
| [Elixir 1.8.1](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) |
| [Postgres 10.3](https://www.postgresql.org/) | `brew install postgresql` | [Postgres Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L187) |
| [Node.js 10.x.x](https://nodejs.org/en/) | `brew install node` | [Node.js Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L66) |
| [Automake](https://www.gnu.org/software/automake/) | `brew install automake` | [Automake Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L72) |
| [Libtool](https://www.gnu.org/software/libtool/) | `brew install libtool` | [Libtool Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L62) |
| [Inotify-tools](https://github.com/rvoicilas/inotify-tools/wiki) | Not Required | Ubuntu - `apt-get install inotify-tools` |
| [GCC Compiler](https://gcc.gnu.org/) | `brew install gcc` | [GCC Compiler Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L70) |
| [GMP](https://gmplib.org/) | `brew install gmp` | [Install GMP Devel](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L74) |
### Build and Run
#### Playbook Deployment
We use [Ansible](https://docs.ansible.com/ansible/latest/index.html) & [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout. See [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform) for details and instructions.
#### Manual Deployment
See [Manual BlockScout Deployment](https://forum.poa.network/t/manual-blockscout-deployment/2458) for instructions.
#### Environment Variables
Our forum contains a [full list of BlockScout environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814).
#### Configuring EVM Chains
* **CSS:** Update the import instruction in `apps/block_scout_web/assets/css/theme/_variables.scss` to select a preset css file. This is reflected in the `production-${chain}` branch for each instance. For example, in the `production-xdai` branch, it is set to `@import "dai-variables"`.
* **ENV:** Update the [environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814) to match the chain specs.
#### Automating Restarts
By default `blockscout` does not restart if it crashes. To enable automated
restarts, set the environment variable `HEART_COMMAND` to whatever command you run to start `blockscout`. Configure the heart beat timeout to change how long it waits before considering the application unresponsive. At that point, it will kill the current blockscout instance and execute the `HEART_COMMAND`. By default a crash dump is not written unless you set `ERL_CRASH_DUMP_SECONDS` to a positive or negative integer. See the [heart](http://erlang.org/doc/man/heart.html) documentation for more information.
#### CircleCI Updates
To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with the following url: [`https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604)
## Testing
### Requirements
* PhantomJS (for wallaby)
### Running the tests
1. Build the assets.
`cd apps/block_scout_web/assets && npm run build; cd -`
2. Format the Elixir code.
`mix format`
3. Run the test suite with coverage for whole umbrella project. This step can be run with different configuration outlined below.
`mix coveralls.html --umbrella`
4. Lint the Elixir code.
`mix credo --strict`
5. Run the dialyzer.
`mix dialyzer --halt-exit-status`
6. Check the Elixir code for vulnerabilities.
`cd apps/explorer && mix sobelow --config; cd -`
`cd apps/block_scout_web && mix sobelow --config; cd -`
7. Lint the JavaScript code.
`cd apps/block_scout_web/assets && npm run eslint; cd -`
8. Test the JavaScript code.
`cd apps/block_scout_web/assets && npm run test; cd -`
See the [project documentation](https://poanetwork.github.io/blockscout) for instructions:
- [Requirements](https://poanetwork.github.io/blockscout/#/requirements)
- [Ansible deployment](https://poanetwork.github.io/blockscout/#/ansible-deployment)
- [Manual deployment](https://poanetwork.github.io/blockscout/#/manual-deployment)
- [ENV variables](https://poanetwork.github.io/blockscout/#/env-variables)
- [Configuration options](https://poanetwork.github.io/blockscout/#/dev-env)
#### Parity
##### Mox
**This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**:
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.Mox
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox
mix coveralls.html --umbrella --exclude no_parity
```
##### HTTP / WebSocket
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Parity
mix coveralls.html --umbrella --exclude no_parity
```
| Protocol | URL |
|:----------|:-----------------------------------|
| HTTP | `http://localhost:8545` |
| WebSocket | `ws://localhost:8546` |
#### Geth
##### Mox
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox
mix coveralls.html --umbrella --exclude no_geth
```
##### HTTP / WebSocket
```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket
export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Geth
mix coveralls.html --umbrella --exclude no_geth
```
| Protocol | URL |
|:----------|:--------------------------------------------------|
| HTTP | `https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY` |
| WebSocket | `wss://mainnet.infura.io/ws/8lTvJTKmHPCHazkneJsY` |
### API Documentation
To view Modules and API Reference documentation:
1. Generate documentation.
`mix docs`
2. View the generated docs.
`open doc/index.html`
## Front-end
### Javascript
All Javascript files are under [apps/block_scout_web/assets/js](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js) and the main file is [app.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/app.js). This file imports all javascript used in the application. If you want to create a new JS file consider creating into [/js/pages](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/pages) or [/js/lib](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/lib), as follows:
#### js/lib
This folder contains all scripts that can be reused in any page or can be used as a helper to some component.
#### js/pages
This folder contains the scripts that are specific for some page.
#### Redux
This project uses Redux to control the state in some pages. There are pages that have things happening in real-time thanks to the Phoenix channels, e.g. Address page, so the page state changes a lot depending on which events it is listening. The redux is also used to load some contents asynchronous, see [async_listing_load.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/async_listing_load.js).
To understand how to build new pages that need redux in this project, see the [redux_helpers.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/redux_helpers.js)
## Internationalization
The app is currently internationalized. It is only localized to U.S. English. To translate new strings.
1. To setup translation file.
`cd apps/block_scout_web; mix gettext.extract --merge; cd -`
2. To edit the new strings, go to `apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po`.
## Metrics
BlockScout is setup to export [Prometheus](https://prometheus.io/) metrics at `/metrics`.
### Prometheus
1. Install prometheus: `brew install prometheus`
2. Start the web server `iex -S mix phx.server`
3. Start prometheus: `prometheus --config.file=prometheus.yml`
### Grafana
1. Install grafana: `brew install grafana`
2. Install Pie Chart panel plugin: `grafana-cli plugins install grafana-piechart-panel`
3. Start grafana: `brew services start grafana`
4. Add Prometheus as a Data Source
1. `open http://localhost:3000/datasources`
2. Click "+ Add data source"
3. Put "Prometheus" for "Name"
4. Change "Type" to "Prometheus"
5. Set "URL" to "http://localhost:9090"
6. Set "Scrape Interval" to "10s"
5. Add the dashboards from https://github.com/deadtrickster/beam-dashboards:
For each `*.json` file in the repo.
1. `open http://localhost:3000/dashboard/import`
2. Copy the contents of the JSON file in the "Or paste JSON" entry
3. Click "Load"
6. View the dashboards. (You will need to click-around and use BlockScout for the web-related metrics to show up.)
## Tracing
Blockscout supports tracing via
[Spandex](http://git@github.com:spandex-project/spandex.git). Each application
has its own tracer, that is configured internally to that application. In order
to enable it, visit each application's `config/<env>.ex` and update its tracer
configuration to change `disabled?: true` to `disabled?: false`. Do this for
each application you'd like included in your trace data.
Currently, only [Datadog](https://www.datadoghq.com/) is supported as a
tracing backend, but more will be added soon.
### DataDog
If you would like to use DataDog, after enabling `Spandex`, set
`"DATADOG_HOST"` and `"DATADOG_PORT"` environment variables to the
host/port that your Datadog agent is running on. For more information on
Datadog and the Datadog agent, see their
[documentation](https://docs.datadoghq.com/).
### Other
If you want to use a different backend, remove the
`SpandexDatadog.ApiServer` `Supervisor.child_spec` from
`Explorer.Application` and follow any instructions provided in `Spandex`
for setting up that backend.
## Memory Usage
The work queues for building the index of all blocks, balances (coin and token), and internal transactions can grow quite large. By default, the soft-limit is 1 GiB, which can be changed in `apps/indexer/config/config.exs`:
```
config :indexer, memory_limit: 1 <<< 30
```
Memory usage is checked once per minute. If the soft-limit is reached, the shrinkable work queues will shed half their load. The shed load will be restored from the database, the same as when a restart of the server occurs, so rebuilding the work queue will be slower, but use less memory.
If all queues are at their minimum size, then no more memory can be reclaimed and an error will be logged.
## Acknowledgements
@ -317,7 +63,6 @@ We would like to thank the [EthPrize foundation](http://ethprize.io/) for their
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments.
## License
[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

@ -1,20 +0,0 @@
# Upgrading Guide
### Migration scripts
There is in the project a `scripts` folder that contains `SQL` files responsible to migrate data from the database.
This script should be used if you already have an indexed database with a large amount of data.
#### `address_current_token_balances_in_batches.sql`
Is responsible to populate a new table using the `token_balances` table information.
#### `internal_transaction_update_in_batches.sql`
Is responsible to migrate data from the `transactions` table to the `internal_transactions` one in order to improve the application listing performance;
#### `transaction_update_in_baches.sql`
Parity call traces contain the input, but it was not put in the internal_transactions_params.
Enforce input and call_type being non-NULL for calls in new constraints on internal_transactions.

@ -123,8 +123,8 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/verify_other_explorers";
@import "components/errors";
@import "components/log-search";
@import "components/radio";
@import "components/network-selector";
@import "components/new_smart_contract";
@import "components/radio_big";
@import "components/btn_no_border";

@ -21,9 +21,9 @@ $navbar-logo-width: auto !default;
.nav-item {
font-size: 14px;
}
.navbar-nav {
flex-grow: 1;
.navbar-nav {
.nav-link {
align-items: center;
color: $header-links-color;
@ -151,7 +151,13 @@ $navbar-logo-width: auto !default;
}
@include media-breakpoint-up(xl) {
width: 280px;
width: 340px;
}
@media (min-width: 1366px) {
width: 500px;
}
@media (min-width: 1440px) {
width: 580px;
}
}
.input-group-append {
@ -198,7 +204,7 @@ $navbar-logo-width: auto !default;
width: 100%;
.awesomplete {
@include media-breakpoint-down(sm) {
@include media-breakpoint-down(lg) {
width: 100%;
}
}
@ -210,6 +216,11 @@ $navbar-logo-width: auto !default;
.navbar-brand {
margin-left: 0;
flex-shrink: 1;
display: inline-flex;
.navbar-logo {
max-width: 100%;
}
}
.navbar-logo {
@ -227,3 +238,18 @@ $navbar-logo-width: auto !default;
.add-border {
border: 1px solid transparentize($white, 0.30);
}
.navbar-collapse {
justify-content: flex-end;
align-items: flex-start;
flex-shrink: 0;
@media (min-width: 992px) {
align-items: center;
}
}
.navbar-container, .navbar-primary {
@include media-breakpoint-up(xl) {
padding-right: 0;
}
}

@ -0,0 +1,334 @@
$network-selector-overlay-background: $modal-overlay-color !default;
$network-selector-close-color: $primary !default;
$network-selector-horizontal-padding: 28px;
$btn-network-selector-load-more-background: #fff !default;
$btn-network-selector-load-more-color: $primary !default;
$network-selector-search-input-color: #a3a9b5 !default;
$network-selector-tab-active-border-color: $primary !default;
$network-selector-item-icon-dimensions: 30px !default;
.network-selector-visible {
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
}
.network-selector-overlay {
background-color: rgba($network-selector-overlay-background, 0.9);
bottom: 0;
display: none;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 123;
}
.network-selector-overlay-close {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.network-selector-wrapper {
display: flex;
height: 100%;
width: 100%;
}
.network-selector {
background-color: #fff;
display: flex;
flex-direction: column;
flex-grow: 1;
flex-shrink: 1;
margin-left: auto;
max-width: 398px;
min-width: 0;
padding-top: 28px;
position: relative;
transition: right 0.25s ease-out;
z-index: 2;
}
.network-selector-close {
flex-shrink: 0;
padding: 0 $network-selector-horizontal-padding;
margin: 0 0 8px;
svg {
cursor: pointer;
display: block;
margin-left: auto;
}
path {
fill: $network-selector-close-color;
}
}
.network-selector-text-container {
flex-shrink: 0;
margin: 0 0 15px;
padding: 0 $network-selector-horizontal-padding;
}
.network-selector-title {
color: #333;
font-size: 18px;
font-weight: normal;
line-height: 1.2;
margin: 0 0 10px;
padding: 0;
}
.network-selector-text {
color: #a3a9b5;
font-size: 12px;
font-weight: normal;
line-height: 1.67;
margin: 0;
padding: 0;
}
.network-selector-search-container {
align-items: center;
background-color: #f5f6fa;
display: flex;
flex-shrink: 0;
height: 62px;
margin: 0;
padding: 0 $network-selector-horizontal-padding;
path {
flex-grow: 0;
flex-shrink: 0;
fill: $network-selector-search-input-color;
}
}
.network-selector-search-input {
background-color: transparent;
border-color: transparent;
color: #333;
flex-grow: 1;
font-size: 14px;
font-weight: 600;
height: 100%;
outline: none;
padding: 0 20px 0 10px;
&[placeholder]{
color: $network-selector-search-input-color !important;
}
&::-webkit-input-placeholder { /* Chrome/Opera/Safari */
color: $network-selector-search-input-color !important;
}
&::-moz-placeholder { /* Firefox 19+ */
color: $network-selector-search-input-color !important;
}
&:-ms-input-placeholder { /* IE 10+ */
color: $network-selector-search-input-color !important;
}
&:-moz-placeholder { /* Firefox 18- */
color: $network-selector-search-input-color !important;
}
}
.network-selector-tabs-container {
border-bottom: 1px solid $base-border-color;
display: flex;
flex-shrink: 0;
margin: 0 $network-selector-horizontal-padding;
}
.network-selector-tab {
color: #a3a9b5;
cursor: pointer;
flex-shrink: 1;
font-size: 14px;
font-weight: 600;
line-height: 1.2;
min-width: 0;
padding: 20px 18px 15px;
position: relative;
text-align: center;
user-select: none;
white-space: nowrap;
&:hover {
color: #333;
}
&.active {
color: #333;
cursor: default;
&::after {
background-color: $network-selector-tab-active-border-color;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
bottom: 0;
content: "";
height: 4px;
left: 0;
position: absolute;
right: 0;
}
}
}
.network-selector-tab-content {
display: none;
&.active {
display: block;
}
}
.network-selector-item {
border-bottom: 1px solid $base-border-color;
display: flex;
position: relative;
.radio {
cursor: pointer;
margin: 0 15px 0 0;
input[type="radio"] {
cursor: pointer;
}
}
.radio-icon {
margin: 0;
}
&:last-child {
border-bottom: none;
}
}
.network-selector-item-url {
align-items: center;
cursor: pointer;
display: flex;
flex-grow: 1;
margin: 0;
padding: 20px 0;
&:hover {
.network-selector-item-type {
color: #333;
}
}
}
.network-selector-item-icon {
background-color: #dfdfdf;
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: contain;
border-radius: 50%;
flex-grow: 0;
flex-shrink: 0;
height: $network-selector-item-icon-dimensions;
margin: 0 15px 0 0;
width: $network-selector-item-icon-dimensions;
}
.network-selector-item-title {
color: #333;
flex-grow: 1;
font-size: 14px;
font-weight: normal;
line-height: 1.2;
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
user-select: none;
white-space: nowrap;
}
.network-selector-item-type {
color: #a3a9b5;
flex-shrink: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.2;
padding-left: 10px;
text-align: right;
user-select: none;
white-space: nowrap;
}
.network-selector-item-content {
align-items: center;
display: flex;
flex-grow: 1;
}
.network-selector-networks-container {
flex-grow: 1;
flex-shrink: 1;
min-height: 100px;
overflow: auto;
padding: 0 $network-selector-horizontal-padding;
}
.network-selector-load-more-container {
flex-shrink: 1;
padding: 0 $network-selector-horizontal-padding;
.btn-network-selector-load-more {
@include btn-line($btn-network-selector-load-more-background, $btn-network-selector-load-more-color);
width: 100%;
}
}
.network-selector-item-favorite {
align-items: center;
cursor: pointer;
display: flex;
flex-grow: 1;
flex-shrink: 0;
margin: 0;
max-width: 36px;
padding-left: 20px;
position: relative;
input[type="checkbox"] {
cursor: pointer;
height: 100%;
opacity: 0;
position: absolute;
width: 100%;
z-index: 5;
&:checked + svg {
position: relative;
z-index: 1;
path {
fill: #ffb20d;
}
}
}
&:hover {
path {
fill: rgba(#ffb20d, 0.4);
}
}
}
.network-selector-tab-content-empty {
font-size: 16px;
font-weight: 600;
padding: 40px;
text-align: center;
}

@ -0,0 +1,49 @@
$radio-color: $primary !default;
$radio-dimensions: 20px !default;
.radio {
align-items: center;
display: flex;
position: relative;
input[type="radio"] {
height: 100%;
opacity: 0;
position: absolute;
width: 100%;
z-index: 5;
&:checked + .radio-icon::before {
background-color: $radio-color;
border-radius: 50%;
content: "";
height: 12px;
left: 50%;
position: absolute;
top: 50%;
transform: translateX(-50%) translateY(-50%);
width: 12px;
}
}
.radio-icon {
border: 1px solid $base-border-color;
border-radius: 50%;
flex-grow: 0;
flex-shrink: 0;
height: $radio-dimensions;
margin: 0 10px 0 0;
position: relative;
width: $radio-dimensions;
z-index: 1;
}
.radio-text {
font-size: 14px;
font-weight: normal;
line-height: 1.2;
position: relative;
white-space: nowrap;
z-index: 1;
}
}

@ -1,6 +1,45 @@
.transaction-details-address {
font-size: 12px;
font-weight: bold;
line-height: 1.2;
margin: 0 0 12px;
.transaction-bottom-panel {
display: flex;
flex-direction: column;
@media (min-width: 768px) {
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
}
.transaction-bottom-panel {
display: flex;
flex-direction: column;
@media (min-width: 768px) {
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
}
.download-all-transactions {
text-align: center;
color: #a3a9b5;
font-size: 13px;
margin-top: 10px;
@media (min-width: 768px) {
margin-top: 30px;
}
.download-all-transactions-link {
text-decoration: none;
svg {
position: relative;
margin-left: 2px;
top: -3px;
path {
fill: $primary;
}
}
&:hover {
span {
text-decoration: underline;
}
}
}
}

@ -31,6 +31,9 @@ import './pages/chain'
import './pages/pending_transactions'
import './pages/transaction'
import './pages/transactions'
import './pages/favorites'
import './pages/network-search'
import './pages/layout'
import './pages/admin/tasks.js'
@ -56,3 +59,4 @@ import './lib/tooltip'
import './lib/modals'
import './lib/try_api'
import './lib/card_tabs'
import './lib/network_selector'

@ -219,6 +219,14 @@ export const elements = {
$el.hide()
}
},
'[csv-download]': {
render ($el, state) {
if (state.emptyResponse) {
return $el.hide()
}
return $el.show()
}
}
}

@ -0,0 +1,77 @@
import $ from 'jquery'
$(function () {
const mainBody = $('body')
const showNetworkSelector = $('.js-show-network-selector')
const hideNetworkSelector = $('.js-network-selector-close')
const hideNetworkSelectorOverlay = $('.js-network-selector-overlay-close')
const networkSelector = $('.js-network-selector')
const networkSelectorOverlay = $('.js-network-selector-overlay')
const networkSelectorTab = $('.js-network-selector-tab')
const networkSelectorTabContent = $('.js-network-selector-tab-content')
const networkSelectorItemURL = $('.js-network-selector-item-url')
const FADE_IN_DELAY = 250
showNetworkSelector.on('click', (e) => {
e.preventDefault()
openNetworkSelector()
})
hideNetworkSelector.on('click', (e) => {
e.preventDefault()
closeNetworkSelector()
})
hideNetworkSelectorOverlay.on('click', (e) => {
e.preventDefault()
closeNetworkSelector()
})
networkSelectorTab.on('click', function (e) {
e.preventDefault()
setNetworkTab($(this))
})
networkSelectorItemURL.on('click', function (e) {
window.location = $(this).attr('network-selector-item-url')
})
let setNetworkTab = (currentTab) => {
if (currentTab.hasClass('active')) return
networkSelectorTab.removeClass('active')
currentTab.addClass('active')
networkSelectorTabContent.removeClass('active')
$(`[network-selector-tab="${currentTab.attr('network-selector-tab-filter')}"]`).addClass('active')
}
let openNetworkSelector = () => {
mainBody.addClass('network-selector-visible')
networkSelectorOverlay.fadeIn(FADE_IN_DELAY)
setNetworkSelectorVisiblePosition()
}
let closeNetworkSelector = () => {
mainBody.removeClass('network-selector-visible')
networkSelectorOverlay.fadeOut(FADE_IN_DELAY)
setNetworkSelectorHiddenPosition()
}
let getNetworkSelectorWidth = () => {
return parseInt(networkSelector.css('width')) || parseInt(networkSelector.css('max-width'))
}
let setNetworkSelectorHiddenPosition = () => {
return networkSelector.css({ 'right': `-${getNetworkSelectorWidth()}px` })
}
let setNetworkSelectorVisiblePosition = () => {
return networkSelector.css({ 'right': '0' })
}
let init = () => {
setNetworkSelectorHiddenPosition()
}
init()
})

@ -40,6 +40,15 @@ const elements = {
return $el.hide()
}
return $el.show()
}
},
'[data-search]': {
render ($el, state) {
if (state.emptyResponse) {
return $el.hide()
}
return $el.show()
}
}

@ -0,0 +1,58 @@
import $ from 'jquery'
var favoritesContainer = $('.js-favorites-tab')
var favoritesNetworksUrls = []
if (localStorage.getItem('favoritesNetworksUrls') === null) {
localStorage.setItem('favoritesNetworksUrls', JSON.stringify(favoritesNetworksUrls))
} else {
favoritesNetworksUrls = JSON.parse(localStorage.getItem('favoritesNetworksUrls'))
}
$(document).on('change', ".network-selector-item-favorite input[type='checkbox']", function () {
var networkUrl = $(this).attr('data-url')
var thisStatus = $(this).is(':checked')
var workWith = $(".network-selector-item[data-url='" + networkUrl + "'")
// Add new checkbox status to same network in another tabs
$(".network-selector-item-favorite input[data-url='" + networkUrl + "']").prop('checked', thisStatus)
// Clone
var parent = $(".network-selector-item[data-url='" + networkUrl + "'").clone()
// Push or remove favorite networks to array
var found = $.inArray(networkUrl, favoritesNetworksUrls)
if (found < 0 && thisStatus === true) {
favoritesNetworksUrls.push(networkUrl)
} else {
var index = favoritesNetworksUrls.indexOf(networkUrl)
if (index !== -1) {
favoritesNetworksUrls.splice(index, 1)
}
}
// Push to localstorage
var willBePushed = JSON.stringify(favoritesNetworksUrls)
localStorage.setItem('favoritesNetworksUrls', willBePushed)
// Append or remove item from 'favorites' tab
if (thisStatus === true) {
favoritesContainer.append(parent[0])
$('.js-favorites-tab .network-selector-tab-content-empty').hide()
} else {
var willRemoved = favoritesContainer.find(workWith)
willRemoved.remove()
if (favoritesNetworksUrls.length === 0) {
$('.js-favorites-tab .network-selector-tab-content-empty').show()
}
}
})
if (favoritesNetworksUrls.length > 0) {
$('.js-favorites-tab .network-selector-tab-content-empty').hide()
for (var i = 0; i < favoritesNetworksUrls.length + 1; i++) {
$(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").find('input[data-url]').prop('checked', true)
var parent = $(".network-selector-item[data-url='" + favoritesNetworksUrls[i] + "'").clone()
favoritesContainer.append(parent[0])
}
}

@ -0,0 +1,9 @@
import $ from 'jquery'
$(document).click(function (event) {
var clickover = $(event.target)
var _opened = $('.navbar-collapse').hasClass('show')
if (_opened === true && $('.navbar').find(clickover).length < 1) {
$('.navbar-toggler').click()
}
})

@ -0,0 +1,21 @@
import $ from 'jquery'
var networkSearchInput = $('.network-selector-search-input')
var networkSearchInputVal = ''
$(networkSearchInput).on('input', function () {
networkSearchInputVal = $(this).val()
$.expr[':'].Contains = $.expr.createPseudo(function (arg) {
return function (elem) {
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0
}
})
if (networkSearchInputVal === '') {
$('.network-selector-item').show()
} else {
$('.network-selector-item').hide()
$(".network-selector-item:Contains('" + networkSearchInputVal + "')").show()
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 60 60">
<g>
<path d="M.87,4h0V26.18a1.35,1.35,0,0,0,1.35,1.37l9.91,0a1.37,1.37,0,0,0,1.16-2L12,23.19h0a2,2,0,0,0-2.75-.75h0l-.14.08h0a2,2,0,0,1-2.72-.74,2.06,2.06,0,0,1-.16-1.65h0l2.06-6.28h0a2,2,0,0,1,2.31-1.36h0L17,13.8h0a2,2,0,0,1,1.33,1,2,2,0,0,1-.73,2.77h0l-.14.08h0a2.07,2.07,0,0,0-.74,2.8h0L18,22.65a1.34,1.34,0,0,0,2.32,0l5-8.66a1.37,1.37,0,0,0-.49-1.86L5.9,1.06h0A3.36,3.36,0,0,0,.87,4Z"/>
<path d="M50.9,26.32h0L30.58,14.37a1.34,1.34,0,0,0-1.85.5L23.4,24.36a1.47,1.47,0,0,0,1.25,2.19h3.48a1.45,1.45,0,0,0,1.44-1.47V24A1.34,1.34,0,0,1,30.9,22.6h.92a1.33,1.33,0,0,1,1,.46l4.84,5.56a1.38,1.38,0,0,1,0,1.81L32.83,36a1.33,1.33,0,0,1-1,.46H30.9a1.34,1.34,0,0,1-1.33-1.35V34a1.45,1.45,0,0,0-1.44-1.47H24.7a1.47,1.47,0,0,0-1.25,2.19l5.29,9.43a1.33,1.33,0,0,0,1.83.49L50.9,32.67h0A3.7,3.7,0,0,0,50.9,26.32Z"/>
<path d="M5.9,58.94h0L24.76,47.87a1.39,1.39,0,0,0,.5-1.88l-5-8.71a1.34,1.34,0,0,0-2.32,0l-1.62,2.86a1.38,1.38,0,0,0,.5,1.87l1.27.75a1.38,1.38,0,0,1,.5,1.86l-.48.84a1.34,1.34,0,0,1-.89.65L10,47.61a1.34,1.34,0,0,1-1.54-.91L6.08,39.56a1.39,1.39,0,0,1,.11-1.12l.47-.83a1.34,1.34,0,0,1,1.84-.49l1.27.75a1.33,1.33,0,0,0,1.83-.49l1.61-2.83a1.37,1.37,0,0,0-1.16-2l-9.84,0A1.35,1.35,0,0,0,.87,33.82V56h0A3.36,3.36,0,0,0,5.9,58.94Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<path fill="#5C34A2" fill-rule="evenodd" d="M15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15C0 6.716 6.716 0 15 0z"/>
<path fill="#FFF" fill-rule="evenodd" d="M8.438 16.812h-.313v.875h-.007c.001.007.007.01.007.016 0 .164-.14.297-.313.297h-2.5A.305.305 0 0 1 5 17.703c0-.006.006-.009.007-.016H5v-5.374h.007c-.001-.007-.007-.01-.007-.016 0-.164.14-.297.312-.297h3.126c1.38 0 2.5 1.077 2.5 2.406 0 1.329-1.12 2.406-2.5 2.406zM14.688 12c1.725 0 3.125 1.343 3.125 3s-1.4 3-3.125 3c-1.726 0-3.126-1.343-3.126-3s1.4-3 3.126-3zM25 17.81a.19.19 0 0 1-.193.185h-7.108c-.004.001-.007.005-.011.005a.188.188 0 0 1-.188-.188c0-.039.021-.07.042-.101l-.007-.012 3.52-5.549h.016c.02-.082.086-.148.177-.148.092 0 .158.066.178.148h.016L25 17.736l-.02.027c.005.017.02.029.02.047z"/>
</svg>

After

Width:  |  Height:  |  Size: 868 B

@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="25" viewBox="0 0 43.5 43.5">
<defs>
<style>
.a {
fill: #fff;
filter: url(#b);
}
.b {
fill: url(#a);
}
</style>
<linearGradient id="a" x1="21" y1="12.89" x2="21" y2="28.58" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fec54c"/>
<stop offset="1" stop-color="#fe9314"/>
</linearGradient>
<filter filterUnits="userSpaceOnUse" height="49" id="b" width="43" x="0" y="0">
<feOffset dy="3" in="SourceAlpha"></feOffset>
<feGaussianBlur result="blurOut" stdDeviation="2.236"></feGaussianBlur>
<feFlood flood-color="#ABBAC7" result="floodOut"></feFlood>
<feComposite in="floodOut" in2="blurOut" operator="atop"></feComposite>
<feComponentTransfer>
<feFuncA slope=".2" type="linear"></feFuncA>
</feComponentTransfer>
<feMerge>
<feMergeNode></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<path class="a" d="M17.2,38.8,6,32.1a4.34,4.34,0,0,1-2-3.4V12.9A4.11,4.11,0,0,1,6,9.5L17.2,2.8C21,.5,21,.5,24.9,2.8L36,9.4a4.22,4.22,0,0,1,2,3.4V28.6A4.22,4.22,0,0,1,36,32L24.9,38.7C21,41.1,21,41.1,17.2,38.8Z"/>
<polygon class="b" points="30.03 12.89 24.97 12.89 21.03 17.64 17.03 12.89 11.97 12.89 18.53 20.82 11.97 28.7 17.03 28.7 20.97 23.95 24.97 28.7 30.03 28.7 23.47 20.76 30.03 12.89"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<path fill="#38A9F5" fill-rule="evenodd" d="M15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15C0 6.716 6.716 0 15 0z"/>
<path fill="#FFF" fill-rule="evenodd" d="M24.22 20h-6.449c-.089 0-.178-.034-.266-.06l.013.06h-2.781l-.836-3.687H5.437v-.078c-.245-.038-.437-.277-.437-.579v-4.469h.003c.007-.65.448-1.173.997-1.183V10h15.95c.558 0 1.133.535 1.282 1.194l1.729 7.612c.149.659-.182 1.194-.741 1.194zM9.054 11.84H6.776l.61 2.638h2.279l-.611-2.638zm3.78 0h-2.279l.611 2.638h2.278l-.61-2.638zm6.226 6.841h1.05l-.282-1.308h-1.05l.282 1.308zm2.761-6.841h-4.375l1.175 5.001h4.376l-1.176-5.001zm1.343 5.533h-1.05l.282 1.308h1.05l-.282-1.308zm-17.727.002H11.664v-.003h1.452L13.668 20H6.125v-.026c-.038.005-.071.026-.109.026-.51 0-.914-.452-.987-1.031H5v-.813h.082c-.038-.077-.082-.153-.082-.25 0-.293.196-.531.437-.531z"/>
</svg>

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<path fill="#6EC9B9" fill-rule="evenodd" d="M15 0c8.284 0 15 6.716 15 15 0 8.284-6.716 15-15 15-8.284 0-15-6.716-15-15C0 6.716 6.716 0 15 0z"/>
<path fill="#FFF" fill-rule="evenodd" d="M24.22 20h-6.449c-.089 0-.178-.034-.266-.06l.013.06h-2.781l-.836-3.687H5.437v-.078c-.245-.038-.437-.277-.437-.579v-4.469h.003c.007-.65.448-1.173.997-1.183V10h15.95c.558 0 1.133.535 1.282 1.194l1.729 7.612c.149.659-.182 1.194-.741 1.194zM9.054 11.84H6.776l.61 2.637h2.279l-.611-2.637zm3.78 0h-2.279l.611 2.637h2.278l-.61-2.637zm6.226 6.841h1.05l-.282-1.308h-1.05l.282 1.308zm2.761-6.841h-4.375l1.175 5.001h4.376l-1.176-5.001zm1.343 5.533h-1.05l.282 1.308h1.05l-.282-1.308zm-17.727.002H11.664v-.003h1.452L13.668 20H6.125v-.026c-.038.005-.071.026-.109.026-.51 0-.914-.452-.987-1.031H5v-.813h.082c-.038-.077-.082-.153-.082-.25 0-.293.196-.531.437-.531z"/>
</svg>

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -41,7 +41,7 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
]
],
url: [
host: "localhost",
host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/"
],
render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)],

@ -10,6 +10,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
alias BlockScoutWeb.TransactionView
alias Explorer.{Chain, Market}
alias Explorer.Chain.{AddressTokenTransferCsvExporter, AddressTransactionCsvExporter}
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
alias Phoenix.View
@ -106,4 +107,44 @@ defmodule BlockScoutWeb.AddressTransactionController do
not_found(conn)
end
end
def token_transfers_csv(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
address
|> AddressTokenTransferCsvExporter.export()
|> Enum.into(
conn
|> put_resp_content_type("application/csv")
|> put_resp_header("content-disposition", "attachment; filename=token_transfers.csv")
|> send_chunked(200)
)
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
def transactions_csv(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
address
|> AddressTransactionCsvExporter.export()
|> Enum.into(
conn
|> put_resp_content_type("application/csv")
|> put_resp_header("content-disposition", "attachment; filename=transactions.csv")
|> send_chunked(200)
)
else
:error ->
unprocessable_entity(conn)
{:error, :not_found} ->
not_found(conn)
end
end
end

@ -8,18 +8,20 @@ defmodule BlockScoutWeb.Chain.MarketHistoryChartController do
with true <- ajax?(conn) do
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
recent_market_history = Market.fetch_recent_history()
market_history_data =
30
|> Market.fetch_recent_history()
|> case do
[today | the_rest] -> [%{today | closing_price: exchange_rate.usd_value} | the_rest]
data -> data
case recent_market_history do
[today | the_rest] ->
encode_market_history_data([%{today | closing_price: exchange_rate.usd_value} | the_rest])
data ->
encode_market_history_data(data)
end
|> encode_market_history_data()
json(conn, %{
history_data: market_history_data,
supply_data: available_supply(Chain.supply_for_days(30), exchange_rate)
supply_data: available_supply(Chain.supply_for_days(), exchange_rate)
})
else
_ -> unprocessable_entity(conn)

@ -4,6 +4,7 @@ defmodule BlockScoutWeb.ChainController do
alias BlockScoutWeb.ChainView
alias Explorer.{Chain, PagingOptions, Repo}
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.Supply.RSK
alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token
alias Explorer.Market
@ -13,6 +14,15 @@ defmodule BlockScoutWeb.ChainController do
transaction_estimated_count = Chain.transaction_estimated_count()
block_count = Chain.block_estimated_count()
market_cap_calculation =
case Application.get_env(:explorer, :supply) do
RSK ->
RSK
_ ->
:standard
end
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
render(
@ -22,6 +32,7 @@ defmodule BlockScoutWeb.ChainController do
average_block_time: AverageBlockTime.average_block_time(),
exchange_rate: exchange_rate,
chart_data_path: market_history_chart_path(conn, :show),
market_cap_calculation: market_cap_calculation,
transaction_estimated_count: transaction_estimated_count,
transactions_path: recent_transactions_path(conn, :index),
block_count: block_count

@ -37,7 +37,7 @@ defmodule BlockScoutWeb.Notifier do
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
market_history_data =
case Market.fetch_recent_history(30) do
case Market.fetch_recent_history() do
[today | the_rest] -> [%{today | closing_price: exchange_rate.usd_value} | the_rest]
data -> data
end

@ -242,8 +242,12 @@ defmodule BlockScoutWeb.Router do
get("/search_logs", AddressLogsController, :search_logs)
get("/transactions_csv", AddressTransactionController, :transactions_csv)
get("/token_autocomplete", ChainController, :token_autocomplete)
get("/token_transfers_csv", AddressTransactionController, :token_transfers_csv)
get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)
get("/api_docs", APIDocsController, :index)

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

@ -7,8 +7,8 @@
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h2 class="card-title"><%= gettext "Logs" %></h2>
<div class="logs-topbar">
<div class="logs-search">
<div data-search class="logs-search">
<input data-search-field class="logs-search-input" type='text' placeholder=<%= gettext("Topic") %> id='search-text-input' />
<button data-cancel-search-button class="logs-search-btn-cancel" type="button">x</button>
<button data-search-button class="logs-search-btn" type="button"><%= gettext("Search") %></button>

@ -21,7 +21,17 @@
<div data-items></div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div class="transaction-bottom-panel">
<div csv-download class="download-all-transactions">
Download <a class="download-all-transactions-link" href=<%= address_transaction_path(@conn, :token_transfers_csv, %{"address_id" => to_string(@address.hash)}) %>><%= gettext("CSV") %></span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16">
<path fill="#333333" fill-rule="evenodd" d="M13 16H1c-.999 0-1-1-1-1V1s-.004-1 1-1h6l7 7v8s-.032 1-1 1zm-1-8c0-.99-1-1-1-1H8s-1 .001-1-1V3c0-.999-1-1-1-1H2v12h10V8z"/>
</svg>
</a>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
</div>
</section>

@ -51,6 +51,8 @@
</div>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<button data-error-message class="alert alert-danger col-12 text-left" style="display: none;">
@ -64,8 +66,17 @@
</div>
<div data-items></div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div class="transaction-bottom-panel">
<div class="download-all-transactions">
Download <a class="download-all-transactions-link" href=<%= address_transaction_path(@conn, :token_transfers_csv, %{"address_id" => to_string(@address.hash)}) %>><%= gettext("CSV") %></span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16">
<path fill="#333333" fill-rule="evenodd" d="M13 16H1c-.999 0-1-1-1-1V1s-.004-1 1-1h6l7 7v8s-.032 1-1 1zm-1-8c0-.99-1-1-1-1H8s-1 .001-1-1V3c0-.999-1-1-1-1H2v12h10V8z"/>
</svg>
</a>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
</div>

@ -2,9 +2,9 @@
<div class="card">
<div class="card-body">
<h1 class="card-title margin-bottom-sm"><%= gettext("ETH RPC API Documentation") %></h2>
<p class="api-text-monospace" data-endpoint-url="<%= BlockScoutWeb.Endpoint.url() %>/api/eth_rpc">[ <%= gettext "Base URL:" %> <%= @conn.host %>/api/eth_rpc ]</p>
<p class="api-text-monospace" data-endpoint-url="<%= BlockScoutWeb.Endpoint.url() %>/api/eth_rpc">[ <%= gettext "Base URL:" %> <%= url() %>/api/eth_rpc ]</p>
<p class="card-subtitle margin-bottom-0">
<%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %>
<%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %>
<a href="https://github.com/ethereum/wiki/wiki/JSON-RPC"><%= gettext "here." %></a>
<%= gettext "This is useful to allow sending requests to blockscout without having to change anything about the request." %>

@ -2,7 +2,7 @@
<div class="card">
<div class="card-body">
<h1 class="card-title margin-bottom-sm"><%= gettext("API Documentation") %></h2>
<p class="api-text-monospace" data-endpoint-url="<%= BlockScoutWeb.Endpoint.url() %>/api">[ <%= gettext "Base URL:" %> <%= @conn.host %>/api ]</p>
<p class="api-text-monospace" data-endpoint-url="<%= BlockScoutWeb.Endpoint.url() %>/api">[ <%= gettext "Base URL:" %> <%= url() %>/api ]</p>
<p class="card-subtitle margin-bottom-0"><%= gettext "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." %></p>
</div>
<div class="api-anchors-list">

@ -30,7 +30,7 @@
<span class="dashboard-banner-chart-legend-label">
<%= gettext "Market Cap" %>
</span>
<span class="dashboard-banner-chart-legend-value" data-selector="market-cap" data-usd-value="<%= @exchange_rate.market_cap_usd %>">
<span class="dashboard-banner-chart-legend-value" data-selector="market-cap" data-usd-value="<%= market_cap(@market_cap_calculation, @exchange_rate) %>">
</span>
</div>
</div>

@ -0,0 +1,53 @@
<div class="network-selector-overlay js-network-selector-overlay">
<div class="network-selector-overlay-close js-network-selector-overlay-close"></div>
<div class="network-selector-wrapper">
<div class="network-selector js-network-selector">
<div class="network-selector-close js-network-selector-close">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13">
<path fill-rule="evenodd" d="M7.881 6.5l4.834 4.834a.977.977 0 0 1-1.381 1.381L6.5 7.881l-4.834 4.834a.977.977 0 0 1-1.381-1.381L5.119 6.5.285 1.666A.977.977 0 0 1 1.666.285L6.5 5.119 11.334.285a.977.977 0 0 1 1.381 1.381L7.881 6.5z"/>
</svg>
</div>
<div class="network-selector-text-container">
<h1 class="network-selector-title"><%= gettext("Change Network") %></h1>
<p class="network-selector-text"><%= gettext("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.") %></p>
</div>
<form class="network-selector-search-container">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="17">
<path fill-rule="evenodd" d="M15.713 15.727a.982.982 0 0 1-1.388 0l-2.289-2.29C10.773 14.403 9.213 15 7.5 15A7.5 7.5 0 1 1 15 7.5c0 1.719-.602 3.285-1.575 4.55l2.288 2.288a.983.983 0 0 1 0 1.389zM7.5 2a5.5 5.5 0 1 0 0 11 5.5 5.5 0 1 0 0-11z"/>
</svg>
<input class="network-selector-search-input" type="text" placeholder='<%= gettext("Search network") %>' />
</form>
<div class="network-selector-tabs-container">
<div class="network-selector-tab js-network-selector-tab active" network-selector-tab-filter="all"><%= gettext("All") %></div>
<div class="network-selector-tab js-network-selector-tab" network-selector-tab-filter="mainnet"><%= gettext("Mainnet") %></div>
<div class="network-selector-tab js-network-selector-tab" network-selector-tab-filter="testnet"><%= gettext("Testnet") %></div>
<div class="network-selector-tab js-network-selector-tab" network-selector-tab-filter="favorites"><%= gettext("Favorites") %></div>
</div>
<div class="network-selector-networks-container">
<% main_nets = dropdown_main_nets() %>
<% test_nets = dropdown_test_nets() %>
<div class="network-selector-tab-content js-network-selector-tab-content active" network-selector-tab="all">
<%= for %{url: url, title: title} <- main_nets do %>
<%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %>
<% end %>
<%= for %{url: url, title: title} <- test_nets do %>
<%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %>
<% end %>
</div>
<div class="network-selector-tab-content js-network-selector-tab-content" network-selector-tab="mainnet">
<%= for %{url: url, title: title} <- main_nets do %>
<%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Mainnet" %>
<% end %>
</div>
<div class="network-selector-tab-content js-network-selector-tab-content" network-selector-tab="testnet">
<%= for %{url: url, title: title} <- test_nets do %>
<%= render BlockScoutWeb.LayoutView, "_network_selector_item.html", title: title, url: url, tab_type: "Testnet" %>
<% end %>
</div>
<div class="network-selector-tab-content js-network-selector-tab-content js-favorites-tab" network-selector-tab="favorites">
<div class="network-selector-tab-content-empty">No content.</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,25 @@
<div class="network-selector-item" data-url="<%= @url %>" data-name="<%= @title %>">
<label class="network-selector-item-url js-network-selector-item-url" network-selector-item-url="<%= @url %>">
<span class="radio">
<input type="radio" name="networkSelectorItem" <%= if @title == subnetwork_title() do %> checked="true" <% end %> />
<span class="radio-icon"></span>
</span>
<span class="network-selector-item-content">
<span class='network-selector-item-icon network-selector-item-icon-<%= String.downcase(String.replace(@title, " ", "-")) %>' style="background-image: url('/images/network-selector-icons/<%= String.downcase(String.replace(@title, " ", "-")) %>.png');"></span>
<span class="network-selector-item-title">
<%= @title %>
</span>
<%= if @tab_type do %>
<span class="network-selector-item-type">
<%= @tab_type %>
</span>
<% end %>
</span>
</label>
<label class="network-selector-item-favorite">
<input type="checkbox" data-url="<%= @url %>" />
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="15">
<path fill="#E2E5EC" fill-rule="evenodd" d="M15.647 6.795c.315-.3.426-.741.29-1.151a1.135 1.135 0 0 0-.926-.764l-3.871-.551a.501.501 0 0 1-.381-.271L9.028.624A1.143 1.143 0 0 0 8-.001c-.44 0-.834.24-1.028.625L5.24 4.059a.506.506 0 0 1-.381.271l-3.871.55c-.435.062-.79.355-.926.765-.136.409-.025.85.29 1.15l2.801 2.673a.492.492 0 0 1 .146.439l-.661 3.774c-.058.333.031.656.25.911.342.397.937.518 1.414.272l3.462-1.782a.53.53 0 0 1 .471 0l3.463 1.782a1.16 1.16 0 0 0 1.413-.272c.22-.255.309-.579.25-.911L12.7 9.907a.489.489 0 0 1 .146-.439l2.801-2.673z"/>
</svg>
</label>
</div>

@ -1,5 +1,5 @@
<nav class="navbar navbar-dark navbar-expand-lg navbar-primary" data-selector="navbar">
<div class="container">
<div class="container-fluid navbar-container">
<%= link to: chain_path(@conn, :show), class: "navbar-brand", "data-test": "header_logo" do %>
<img class="navbar-logo" src="<%= logo() %>" alt="<%= subnetwork_title() %>" />
<% end %>
@ -7,7 +7,7 @@
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link topnav-nav-link dropdown-toggle" href="#" id="navbarBlocksDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon">
@ -82,26 +82,12 @@
</div>
</li>
<li class="nav-item dropdown nav-item-networks">
<a class="nav-link topnav-nav-link <%= if dropdown_nets() == [], do: "disabled", else: "dropdown-toggle" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<a class="nav-link topnav-nav-link dropdown-toggle active-icon js-show-network-selector <%= if dropdown_nets() == [], do: "disabled" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_active_icon.html" %>
</span>
<%= subnetwork_title() %>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item header division">Mainnets</a>
<%= for %{url: url, title: title} <- dropdown_head_main_nets() do %>
<a class="dropdown-item" href="<%= url%>"><%= title %></a>
<% end %>
<a class="dropdown-item header division">Testnets</a>
<%= for %{url: url, title: title} <- test_nets(dropdown_nets()) do %>
<a class="dropdown-item" href="<%= url%>"><%= title %></a>
<% end %>
<a class="dropdown-item header division">Other Networks</a>
<%= for %{url: url, title: title} <- dropdown_other_nets() do %>
<a class="dropdown-item" href="<%= url%>"><%= title %></a>
<% end %>
</div>
</li>
</ul>
<!-- Search navbar -->
@ -136,3 +122,4 @@
</div>
</div>
</nav>
<%= render BlockScoutWeb.LayoutView, "_network_selector.html" %>

@ -1,6 +1,6 @@
<section>
<section class="address-overview">
<div class="row">
<div class="col-md-12 col-lg-8">
<div class="card-section col-md-12 col-lg-8 pr-0-md">
<div class="card">
<div class="card-body">
<h1 class="card-title">
@ -11,7 +11,7 @@
<% end %>
<!-- buttons -->
<span class="overview-title-buttons float-right">
<span data-clipboard-text="<%= @token.contract_address_hash %>">
<span class="overview-title-item" data-clipboard-text="<%= @token.contract_address_hash %>">
<span
aria-label='<%= gettext("Copy Address") %>'
class="btn-copy-icon"
@ -25,6 +25,7 @@
</span>
</span>
<span
class="overview-title-item"
data-target="#qrModal"
data-toggle="modal"
>
@ -68,7 +69,7 @@
</div>
<%= if total_supply?(@token) do %>
<div class="col-md-6 col-lg-4">
<div class="card-section col-md-12 col-lg-4 pl-0-md">
<div class="card card-background-1">
<div class="card-body">
<h2 class="card-title balance-card-title"><%= gettext "Total Supply" %></h2>

@ -170,17 +170,21 @@
</div>
<%= case token_transfer_type(@transaction) do %>
<% {type, token_transfer} -> %>
<% {type, transaction_with_transfers} when is_atom(type) -> %>
<div class="col-md-12 col-lg-4 d-flex flex-column flex-md-row flex-lg-column pl-0">
<!-- Value -->
<div class="card card-background-1 flex-grow-1">
<div class="card-body card-body-flex-column-space-between">
<h2 class="card-title balance-card-title"><%= if type == :erc20, do: gettext("ERC-20"), else: gettext("ERC-721")%><%= gettext " Token Transfer" %></h2>
<h2 class="card-title balance-card-title"><%= token_type_name(type)%><%= gettext " Token Transfer" %></h2>
<div class="text-right">
<%= for transfer <- transaction_with_transfers.token_transfers do %>
<h3 class="address-balance-text">
<%= token_transfer_amount(token_transfer) %>
<%= link(token_symbol(token_transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, token_transfer.token.contract_address_hash)) %>
<%= token_transfer_amount(transfer) %>
<%= link(token_symbol(transfer.token), to: token_path(BlockScoutWeb.Endpoint, :show, transfer.token.contract_address_hash)) %>
</h3>
<% end %>
</div>
</div>
</div>

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.APIDocsView do
use BlockScoutWeb, :view
alias BlockScoutWeb.LayoutView
alias BlockScoutWeb.{Endpoint, LayoutView}
def action_tile_id(module, action) do
"#{module}-#{action}"
@ -33,4 +33,12 @@ defmodule BlockScoutWeb.APIDocsView do
"&#{param.key}=" <> "{<strong>#{param.placeholder}</strong>}"
end)
end
defp url do
if System.get_env("BLOCKSCOUT_HOST") do
"http://" <> System.get_env("BLOCKSCOUT_HOST")
else
Endpoint.url()
end
end
end

@ -2,4 +2,12 @@ defmodule BlockScoutWeb.ChainView do
use BlockScoutWeb, :view
alias BlockScoutWeb.LayoutView
defp market_cap(:standard, exchange_rate) do
exchange_rate.market_cap_usd
end
defp market_cap(module, exchange_rate) do
module.market_cap(exchange_rate)
end
end

@ -198,6 +198,16 @@ defmodule BlockScoutWeb.LayoutView do
|> Enum.reject(&Map.get(&1, :hide_in_dropdown?))
end
def dropdown_main_nets do
dropdown_nets()
|> main_nets()
end
def dropdown_test_nets do
dropdown_nets()
|> test_nets()
end
def dropdown_head_main_nets do
dropdown_nets()
|> main_nets()

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.TransactionView do
alias BlockScoutWeb.{AddressView, BlockView, TabHelpers}
alias Cldr.Number
alias Explorer.Chain
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei}
alias Explorer.ExchangeRates.Token
@ -33,7 +33,18 @@ defmodule BlockScoutWeb.TransactionView do
def value_transfer?(_), do: false
def token_transfer_type(transaction) do
Chain.transaction_token_transfer_type(transaction)
transaction_with_transfers = Repo.preload(transaction, token_transfers: :token)
type = Chain.transaction_token_transfer_type(transaction)
if type, do: {type, transaction_with_transfers}
end
def token_type_name(type) do
case type do
:erc20 -> gettext("ERC-20 ")
:erc721 -> gettext("ERC-721 ")
:token_transfer -> ""
end
end
def processing_time_duration(%Transaction{block: nil}) do

@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:133
#: lib/block_scout_web/views/transaction_view.ex:144
msgid "(Awaiting internal transactions for status)"
msgstr ""
@ -105,13 +105,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/index.html.eex:4
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60
msgid "Addresses"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27
#: lib/block_scout_web/templates/address_transaction/index.html.eex:23
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21
#: lib/block_scout_web/views/address_internal_transaction_view.ex:8
#: lib/block_scout_web/views/address_transaction_view.ex:8
msgid "All"
@ -210,8 +211,8 @@ msgstr ""
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:144
#: lib/block_scout_web/templates/address/overview.html.eex:152
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115
msgid "Close"
msgstr ""
@ -275,12 +276,12 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:210
#: lib/block_scout_web/views/transaction_view.ex:221
msgid "Contract Call"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:209
#: lib/block_scout_web/views/transaction_view.ex:220
msgid "Contract Creation"
msgstr ""
@ -363,12 +364,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:137
#: lib/block_scout_web/views/transaction_view.ex:148
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:135
#: lib/block_scout_web/views/transaction_view.ex:146
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@ -378,7 +379,7 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:55
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30
#: lib/block_scout_web/templates/transaction/overview.html.eex:192
#: lib/block_scout_web/templates/transaction/overview.html.eex:196
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@ -473,7 +474,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:306
#: lib/block_scout_web/views/transaction_view.ex:263
#: lib/block_scout_web/views/transaction_view.ex:274
msgid "Internal Transactions"
msgstr ""
@ -490,7 +491,7 @@ msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:220
#: lib/block_scout_web/templates/transaction/overview.html.eex:224
msgid "Limit"
msgstr ""
@ -500,7 +501,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:312
#: lib/block_scout_web/views/transaction_view.ex:264
#: lib/block_scout_web/views/transaction_view.ex:275
msgid "Logs"
msgstr ""
@ -512,7 +513,7 @@ msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:118
#: lib/block_scout_web/views/transaction_view.ex:129
msgid "Max of"
msgstr ""
@ -602,8 +603,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:44
#: lib/block_scout_web/views/transaction_view.ex:132
#: lib/block_scout_web/views/transaction_view.ex:166
#: lib/block_scout_web/views/transaction_view.ex:143
#: lib/block_scout_web/views/transaction_view.ex:177
msgid "Pending"
msgstr ""
@ -626,8 +627,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:33
#: lib/block_scout_web/templates/address/overview.html.eex:143
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106
msgid "QR Code"
msgstr ""
@ -666,8 +667,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
#: lib/block_scout_web/templates/layout/_topnav.html.eex:116
#: lib/block_scout_web/templates/layout/_topnav.html.eex:133
#: lib/block_scout_web/templates/layout/_topnav.html.eex:102
#: lib/block_scout_web/templates/layout/_topnav.html.eex:119
msgid "Search"
msgstr ""
@ -684,13 +685,13 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:34
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:37
msgid "Show QR Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:134
#: lib/block_scout_web/views/transaction_view.ex:145
msgid "Success"
msgstr ""
@ -743,7 +744,7 @@ msgid "There are no tokens."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:62
#: lib/block_scout_web/templates/address_transaction/index.html.eex:64
msgid "There are no transactions for this address."
msgstr ""
@ -795,7 +796,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:208
#: lib/block_scout_web/views/transaction_view.ex:219
msgid "Token Transfer"
msgstr ""
@ -805,7 +806,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:262
#: lib/block_scout_web/views/transaction_view.ex:273
msgid "Token Transfers"
msgstr ""
@ -834,7 +835,7 @@ msgid "Total Difficulty"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:74
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75
msgid "Total Supply"
msgstr ""
@ -845,7 +846,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:3
#: lib/block_scout_web/views/transaction_view.ex:211
#: lib/block_scout_web/views/transaction_view.ex:222
msgid "Transaction"
msgstr ""
@ -881,7 +882,7 @@ msgid "Transactions sent"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:61
msgid "Transfers"
msgstr ""
@ -907,7 +908,7 @@ msgid "Unique Token"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:214
#: lib/block_scout_web/templates/transaction/overview.html.eex:218
msgid "Used"
msgstr ""
@ -927,7 +928,7 @@ msgid "Validations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:192
#: lib/block_scout_web/templates/transaction/overview.html.eex:196
msgid "Value"
msgstr ""
@ -943,7 +944,7 @@ msgid "Verify & publish"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:54
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55
msgid "View Contract"
msgstr ""
@ -1048,7 +1049,7 @@ msgid "Self-Destruct"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63
msgid "Decimals"
msgstr ""
@ -1210,7 +1211,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:21
#: lib/block_scout_web/templates/address_token/index.html.eex:13
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:57
#: lib/block_scout_web/templates/address_transaction/index.html.eex:59
#: lib/block_scout_web/templates/address_validation/index.html.eex:22
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/chain/show.html.eex:91
@ -1480,8 +1481,8 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:110
#: lib/block_scout_web/templates/layout/_topnav.html.eex:114
#: lib/block_scout_web/templates/layout/_topnav.html.eex:96
#: lib/block_scout_web/templates/layout/_topnav.html.eex:100
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
@ -1520,16 +1521,6 @@ msgstr ""
msgid "Optimization runs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:178
msgid "ERC-20"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:178
msgid "ERC-721"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/api_docs/index.html.eex:4
msgid "API Documentation"
@ -1546,14 +1537,14 @@ msgid "View All Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:210
#: lib/block_scout_web/templates/transaction/overview.html.eex:214
msgid "Gas"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
#: lib/block_scout_web/views/transaction_view.ex:265
#: lib/block_scout_web/views/transaction_view.ex:276
msgid "Raw Trace"
msgstr ""
@ -1699,11 +1690,6 @@ msgstr ""
msgid " Token Transfer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27
msgid "There is no decompilded contracts for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14
msgid " is recommended."
@ -1729,6 +1715,11 @@ msgstr ""
msgid "However, in general, the"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27
msgid "There is no decompilded contracts for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7
msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found "
@ -1748,3 +1739,49 @@ msgstr ""
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9
msgid "here."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11
msgid "Change Network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24
msgid "Favorites"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12
msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22
msgid "Mainnet"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18
msgid "Search network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23
msgid "Testnet"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
msgid "CSV"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 "
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:45
msgid "ERC-721 "
msgstr ""

@ -49,7 +49,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:133
#: lib/block_scout_web/views/transaction_view.ex:144
msgid "(Awaiting internal transactions for status)"
msgstr ""
@ -105,13 +105,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/index.html.eex:4
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:59
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60
msgid "Addresses"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27
#: lib/block_scout_web/templates/address_transaction/index.html.eex:23
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21
#: lib/block_scout_web/views/address_internal_transaction_view.ex:8
#: lib/block_scout_web/views/address_transaction_view.ex:8
msgid "All"
@ -210,8 +211,8 @@ msgstr ""
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
#: lib/block_scout_web/templates/address/overview.html.eex:144
#: lib/block_scout_web/templates/address/overview.html.eex:152
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:115
msgid "Close"
msgstr ""
@ -275,12 +276,12 @@ msgid "Contract Address Pending"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:210
#: lib/block_scout_web/views/transaction_view.ex:221
msgid "Contract Call"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:209
#: lib/block_scout_web/views/transaction_view.ex:220
msgid "Contract Creation"
msgstr ""
@ -363,12 +364,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:137
#: lib/block_scout_web/views/transaction_view.ex:148
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:135
#: lib/block_scout_web/views/transaction_view.ex:146
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@ -378,7 +379,7 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:55
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30
#: lib/block_scout_web/templates/transaction/overview.html.eex:192
#: lib/block_scout_web/templates/transaction/overview.html.eex:196
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr "POA"
@ -473,7 +474,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:306
#: lib/block_scout_web/views/transaction_view.ex:263
#: lib/block_scout_web/views/transaction_view.ex:274
msgid "Internal Transactions"
msgstr ""
@ -490,7 +491,7 @@ msgid "Less than"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:220
#: lib/block_scout_web/templates/transaction/overview.html.eex:224
msgid "Limit"
msgstr ""
@ -500,7 +501,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:312
#: lib/block_scout_web/views/transaction_view.ex:264
#: lib/block_scout_web/views/transaction_view.ex:275
msgid "Logs"
msgstr ""
@ -512,7 +513,7 @@ msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:118
#: lib/block_scout_web/views/transaction_view.ex:129
msgid "Max of"
msgstr ""
@ -602,8 +603,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:44
#: lib/block_scout_web/views/transaction_view.ex:132
#: lib/block_scout_web/views/transaction_view.ex:166
#: lib/block_scout_web/views/transaction_view.ex:143
#: lib/block_scout_web/views/transaction_view.ex:177
msgid "Pending"
msgstr ""
@ -626,8 +627,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:33
#: lib/block_scout_web/templates/address/overview.html.eex:143
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106
msgid "QR Code"
msgstr ""
@ -666,8 +667,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
#: lib/block_scout_web/templates/layout/_topnav.html.eex:116
#: lib/block_scout_web/templates/layout/_topnav.html.eex:133
#: lib/block_scout_web/templates/layout/_topnav.html.eex:102
#: lib/block_scout_web/templates/layout/_topnav.html.eex:119
msgid "Search"
msgstr ""
@ -684,13 +685,13 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:34
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:37
msgid "Show QR Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
#: lib/block_scout_web/views/transaction_view.ex:134
#: lib/block_scout_web/views/transaction_view.ex:145
msgid "Success"
msgstr ""
@ -743,7 +744,7 @@ msgid "There are no tokens."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:62
#: lib/block_scout_web/templates/address_transaction/index.html.eex:64
msgid "There are no transactions for this address."
msgstr ""
@ -795,7 +796,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4
#: lib/block_scout_web/views/transaction_view.ex:208
#: lib/block_scout_web/views/transaction_view.ex:219
msgid "Token Transfer"
msgstr ""
@ -805,7 +806,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4
#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7
#: lib/block_scout_web/views/tokens/overview_view.ex:35
#: lib/block_scout_web/views/transaction_view.ex:262
#: lib/block_scout_web/views/transaction_view.ex:273
msgid "Token Transfers"
msgstr ""
@ -834,7 +835,7 @@ msgid "Total Difficulty"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:74
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:75
msgid "Total Supply"
msgstr ""
@ -845,7 +846,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:3
#: lib/block_scout_web/views/transaction_view.ex:211
#: lib/block_scout_web/views/transaction_view.ex:222
msgid "Transaction"
msgstr ""
@ -881,7 +882,7 @@ msgid "Transactions sent"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:60
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:61
msgid "Transfers"
msgstr ""
@ -907,7 +908,7 @@ msgid "Unique Token"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:214
#: lib/block_scout_web/templates/transaction/overview.html.eex:218
msgid "Used"
msgstr ""
@ -927,7 +928,7 @@ msgid "Validations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:192
#: lib/block_scout_web/templates/transaction/overview.html.eex:196
msgid "Value"
msgstr ""
@ -943,7 +944,7 @@ msgid "Verify & publish"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:54
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:55
msgid "View Contract"
msgstr ""
@ -1048,7 +1049,7 @@ msgid "Self-Destruct"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:62
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:63
msgid "Decimals"
msgstr ""
@ -1210,7 +1211,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_logs/index.html.eex:21
#: lib/block_scout_web/templates/address_token/index.html.eex:13
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:57
#: lib/block_scout_web/templates/address_transaction/index.html.eex:59
#: lib/block_scout_web/templates/address_validation/index.html.eex:22
#: lib/block_scout_web/templates/block_transaction/index.html.eex:23
#: lib/block_scout_web/templates/chain/show.html.eex:91
@ -1480,8 +1481,8 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_topnav.html.eex:110
#: lib/block_scout_web/templates/layout/_topnav.html.eex:114
#: lib/block_scout_web/templates/layout/_topnav.html.eex:96
#: lib/block_scout_web/templates/layout/_topnav.html.eex:100
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
@ -1520,16 +1521,6 @@ msgstr ""
msgid "Optimization runs"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:178
msgid "ERC-20"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:178
msgid "ERC-721"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/api_docs/index.html.eex:4
msgid "API Documentation"
@ -1546,14 +1537,14 @@ msgid "View All Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:210
#: lib/block_scout_web/templates/transaction/overview.html.eex:214
msgid "Gas"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
#: lib/block_scout_web/views/transaction_view.ex:265
#: lib/block_scout_web/views/transaction_view.ex:276
msgid "Raw Trace"
msgstr ""
@ -1699,11 +1690,6 @@ msgstr ""
msgid " Token Transfer"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27
msgid "There is no decompilded contracts for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14
msgid " is recommended."
@ -1714,7 +1700,7 @@ msgstr ""
msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences."
msgstr ""
#, elixir-format, fuzzy
#, elixir-format
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4
msgid "ETH RPC API Documentation"
msgstr ""
@ -1729,6 +1715,11 @@ msgstr ""
msgid "However, in general, the"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27
msgid "There is no decompilded contracts for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7
msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found "
@ -1744,7 +1735,53 @@ msgstr ""
msgid "custom RPC"
msgstr ""
#, elixir-format, fuzzy
#, elixir-format
#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9
msgid "here."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11
msgid "Change Network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24
msgid "Favorites"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12
msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22
msgid "Mainnet"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18
msgid "Search network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23
msgid "Testnet"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
msgid "CSV"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 "
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:45
msgid "ERC-721 "
msgstr ""

@ -43,6 +43,8 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
describe "new_rate" do
test "subscribed user is notified", %{token: token} do
ExchangeRates.handle_info({nil, {:ok, [token]}}, %{})
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()})
topic = "exchange_rate:new_rate"
@endpoint.subscribe(topic)
@ -61,6 +63,8 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
test "subscribed user is notified with market history", %{token: token} do
ExchangeRates.handle_info({nil, {:ok, [token]}}, %{})
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Market.MarketHistoryCache.cache_name()})
today = Date.utc_today()
@ -76,6 +80,8 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
Market.bulk_insert_history(records)
Market.fetch_recent_history()
topic = "exchange_rate:new_rate"
@endpoint.subscribe(topic)

@ -132,4 +132,40 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
end)
end
end
describe "GET token_transfers_csv/2" do
test "exports token transfers to csv", %{conn: conn} do
address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:token_transfer, transaction: transaction, from_address: address)
insert(:token_transfer, transaction: transaction, to_address: address)
conn = get(conn, "/token_transfers_csv", %{"address_id" => to_string(address.hash)})
assert conn.resp_body |> String.split("\n") |> Enum.count() == 4
end
end
describe "GET transactions_csv/2" do
test "download csv file with transactions", %{conn: conn} do
address = insert(:address)
:transaction
|> insert(from_address: address)
|> with_block()
:transaction
|> insert(from_address: address)
|> with_block()
conn = get(conn, "/transactions_csv", %{"address_id" => to_string(address.hash)})
assert conn.resp_body |> String.split("\n") |> Enum.count() == 4
end
end
end

@ -12,7 +12,9 @@ config :explorer,
token_functions_reader_max_retries: 3,
allowed_evm_versions:
System.get_env("ALLOWED_EVM_VERSIONS") ||
"homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg"
"homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg",
include_uncles_in_average_block_time:
if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true)
config :explorer, Explorer.Counters.AverageBlockTime, enabled: true
@ -71,8 +73,15 @@ else
config :explorer, Explorer.Staking.EpochCounter, enabled: false
end
if System.get_env("SUPPLY_MODULE") == "TokenBridge" do
config :explorer, supply: Explorer.Chain.Supply.TokenBridge
case System.get_env("SUPPLY_MODULE") do
"TokenBridge" ->
config :explorer, supply: Explorer.Chain.Supply.TokenBridge
"rsk" ->
config :explorer, supply: Explorer.Chain.Supply.RSK
_ ->
:ok
end
if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do

@ -7,6 +7,7 @@ defmodule Explorer.Application do
alias Explorer.Admin
alias Explorer.Chain.{BlockCountCache, BlockNumberCache, BlocksCache, NetVersionCache, TransactionCountCache}
alias Explorer.Market.MarketHistoryCache
alias Explorer.Repo.PrometheusLogger
@impl Application
@ -32,7 +33,8 @@ defmodule Explorer.Application do
{TransactionCountCache, [[], []]},
{BlockCountCache, []},
con_cache_child_spec(BlocksCache.cache_name()),
con_cache_child_spec(NetVersionCache.cache_name())
con_cache_child_spec(NetVersionCache.cache_name()),
con_cache_child_spec(MarketHistoryCache.cache_name())
]
children = base_children ++ configurable_children()

@ -52,6 +52,7 @@ defmodule Explorer.Chain do
alias Explorer.Chain.Block.{EmissionReward, Reward}
alias Explorer.Chain.Import.Runner
alias Explorer.Counters.AddressesWithBalanceCounter
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
alias Dataloader.Ecto, as: DataloaderEcto
@ -2613,7 +2614,7 @@ defmodule Explorer.Chain do
@doc """
Calls supply_for_days from the configured supply_module
"""
def supply_for_days(days_count), do: supply_module().supply_for_days(days_count)
def supply_for_days, do: supply_module().supply_for_days(MarketHistoryCache.recent_days_count())
@doc """
Streams a lists token contract addresses that haven't been cataloged.
@ -2922,7 +2923,7 @@ defmodule Explorer.Chain do
end
@spec transaction_token_transfer_type(Transaction.t()) ::
{:erc20, TokenTransfer.t()} | {:erc721, TokenTransfer.t()} | nil
:erc20 | :erc721 | :token_transfer | nil
def transaction_token_transfer_type(
%Transaction{
status: :ok,
@ -2932,8 +2933,19 @@ defmodule Explorer.Chain do
} = transaction
) do
zero_wei = %Wei{value: Decimal.new(0)}
result = find_token_transfer_type(transaction, input, value)
transaction = Repo.preload(transaction, token_transfers: :token)
if is_nil(result) && Enum.count(transaction.token_transfers) > 0 && value == zero_wei,
do: :token_transfer,
else: result
rescue
_ -> nil
end
def transaction_token_transfer_type(_), do: nil
defp find_token_transfer_type(transaction, input, value) do
zero_wei = %Wei{value: Decimal.new(0)}
# https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC721/ERC721.sol#L35
case {to_string(input), value} do
@ -2958,6 +2970,9 @@ defmodule Explorer.Chain do
find_erc721_token_transfer(transaction.token_transfers, {from_address, to_address})
{"0xf907fc5b" <> _params, ^zero_wei} ->
:erc20
# check for ERC 20 or for old ERC 721 token versions
{unquote(TokenTransfer.transfer_function_signature()) <> params, ^zero_wei} ->
types = [:address, {:uint, 256}]
@ -2971,34 +2986,31 @@ defmodule Explorer.Chain do
_ ->
nil
end
rescue
_ -> nil
end
def transaction_token_transfer_type(_), do: nil
defp find_erc721_token_transfer(token_transfers, {from_address, to_address}) do
token_transfer =
Enum.find(token_transfers, fn token_transfer ->
token_transfer.from_address_hash.bytes == from_address && token_transfer.to_address_hash.bytes == to_address
end)
if token_transfer, do: {:erc721, token_transfer}
if token_transfer, do: :erc721
end
defp find_erc721_or_erc20_token_transfer(token_transfers, {address, decimal_value}) do
token_transfer =
Enum.find(token_transfers, fn token_transfer ->
token_transfer.to_address_hash.bytes == address &&
(token_transfer.amount == decimal_value || token_transfer.token_id)
token_transfer.to_address_hash.bytes == address && token_transfer.amount == decimal_value
end)
if token_transfer do
case token_transfer.token do
%Token{type: "ERC-20"} -> {:erc20, token_transfer}
%Token{type: "ERC-721"} -> {:erc721, token_transfer}
%Token{type: "ERC-20"} -> :erc20
%Token{type: "ERC-721"} -> :erc721
_ -> nil
end
else
:erc20
end
end

@ -0,0 +1,119 @@
defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
@moduledoc """
Exports token transfers to a csv file.
"""
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, TokenTransfer, Transaction}
alias NimbleCSV.RFC4180
@necessity_by_association [
necessity_by_association: %{
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
[token_transfers: :token_contract_address] => :optional,
:block => :required
}
]
@page_size 150
@paging_options %PagingOptions{page_size: @page_size + 1}
def export(address) do
address
|> fetch_all_transactions(@paging_options)
|> to_token_transfers()
|> to_csv_format(address)
|> dump_to_stream()
end
defp fetch_all_transactions(address, paging_options, acc \\ []) do
options = Keyword.merge(@necessity_by_association, paging_options: paging_options)
transactions =
address
|> Chain.address_to_transactions_with_rewards(options)
|> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end)
new_acc = transactions ++ acc
case Enum.split(transactions, @page_size) do
{_transactions, [%Transaction{block_number: block_number, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, index}}
fetch_all_transactions(address, new_paging_options, new_acc)
{_, []} ->
new_acc
end
end
defp to_token_transfers(transactions) do
transactions
|> Enum.flat_map(fn transaction ->
transaction.token_transfers
|> Enum.map(fn transfer -> %{transfer | transaction: transaction} end)
end)
end
defp dump_to_stream(transactions) do
transactions
|> RFC4180.dump_to_stream()
end
defp to_csv_format(token_transfers, address) do
row_names = [
"TxHash",
"BlockNumber",
"UnixTimestamp",
"FromAddress",
"ToAddress",
"TokenContractAddress",
"Type",
"TokenSymbol",
"TokensTransferred",
"TransactionFee",
"Status",
"ErrCode"
]
token_transfer_lists =
token_transfers
|> Stream.map(fn token_transfer ->
[
to_string(token_transfer.transaction_hash),
token_transfer.transaction.block_number,
token_transfer.transaction.block.timestamp,
token_transfer.from_address |> to_string() |> String.downcase(),
token_transfer.to_address |> to_string() |> String.downcase(),
token_transfer.token_contract_address |> to_string() |> String.downcase(),
type(token_transfer, address),
token_transfer.token.symbol,
token_transfer.amount,
fee(token_transfer.transaction),
token_transfer.transaction.status,
token_transfer.transaction.error
]
end)
Stream.concat([row_names], token_transfer_lists)
end
defp type(%TokenTransfer{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT"
defp type(%TokenTransfer{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN"
defp type(_, _), do: ""
defp fee(transaction) do
transaction
|> Chain.fee(:wei)
|> case do
{:actual, value} -> value
{:maximum, value} -> "Max of #{value}"
end
end
end

@ -0,0 +1,139 @@
defmodule Explorer.Chain.AddressTransactionCsvExporter do
@moduledoc """
Exports transactions to a csv file.
"""
import Ecto.Query,
only: [
from: 2
]
alias Explorer.{Chain, Market, PagingOptions, Repo}
alias Explorer.Market.MarketHistory
alias Explorer.Chain.{Address, Transaction, Wei}
alias Explorer.ExchangeRates.Token
alias NimbleCSV.RFC4180
@necessity_by_association [
necessity_by_association: %{
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
[token_transfers: :token_contract_address] => :optional,
:block => :required
}
]
@page_size 150
@paging_options %PagingOptions{page_size: @page_size + 1}
@spec export(Address.t()) :: Enumerable.t()
def export(address) do
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
address
|> fetch_all_transactions(@paging_options)
|> to_csv_format(address, exchange_rate)
|> dump_to_stream()
end
defp fetch_all_transactions(address, paging_options, acc \\ []) do
options = Keyword.merge(@necessity_by_association, paging_options: paging_options)
transactions = Chain.address_to_transactions_with_rewards(address, options)
new_acc = transactions ++ acc
case Enum.split(transactions, @page_size) do
{_transactions, [%Transaction{block_number: block_number, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, index}}
fetch_all_transactions(address, new_paging_options, new_acc)
{_, []} ->
new_acc
end
end
defp dump_to_stream(transactions) do
transactions
|> RFC4180.dump_to_stream()
end
defp to_csv_format(transactions, address, exchange_rate) do
row_names = [
"TxHash",
"BlockNumber",
"UnixTimestamp",
"FromAddress",
"ToAddress",
"ContractAddress",
"Type",
"Value",
"Fee",
"Status",
"ErrCode",
"CurrentPrice",
"TxDateOpeningPrice",
"TxDateClosingPrice"
]
transaction_lists =
transactions
|> Stream.map(fn transaction ->
{opening_price, closing_price} = price_at_date(transaction.block.timestamp)
[
to_string(transaction.hash),
transaction.block_number,
transaction.block.timestamp,
to_string(transaction.from_address),
to_string(transaction.to_address),
to_string(transaction.created_contract_address),
type(transaction, address),
Wei.to(transaction.value, :wei),
fee(transaction),
transaction.status,
transaction.error,
exchange_rate.usd_value,
opening_price,
closing_price
]
end)
Stream.concat([row_names], transaction_lists)
end
defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT"
defp type(%Transaction{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN"
defp type(_, _), do: ""
defp fee(transaction) do
transaction
|> Chain.fee(:wei)
|> case do
{:actual, value} -> value
{:maximum, value} -> "Max of #{value}"
end
end
defp price_at_date(datetime) do
date = DateTime.to_date(datetime)
query =
from(
mh in MarketHistory,
where: mh.date == ^date
)
case Repo.one(query) do
nil -> {nil, nil}
price -> {price.opening_price, price.closing_price}
end
end
end

@ -10,9 +10,9 @@ defmodule Explorer.Chain.Block do
alias Explorer.Chain.{Address, Gas, Hash, Transaction}
alias Explorer.Chain.Block.{Reward, SecondDegreeRelation}
@optional_attrs ~w(internal_transactions_indexed_at size refetch_needed)a
@optional_attrs ~w(internal_transactions_indexed_at size refetch_needed total_difficulty difficulty)a
@required_attrs ~w(consensus difficulty gas_limit gas_used hash miner_hash nonce number parent_hash timestamp total_difficulty)a
@required_attrs ~w(consensus gas_limit gas_used hash miner_hash nonce number parent_hash timestamp)a
@typedoc """
How much work is required to find a hash with some number of leading 0s. It is measured in hashes for PoW

@ -0,0 +1,103 @@
defmodule Explorer.Chain.Supply.RSK do
@moduledoc """
Defines the supply API for calculating supply for coins from RSK.
"""
use Explorer.Chain.Supply
import Ecto.Query, only: [from: 2]
alias Explorer.Chain.Address.CoinBalance
alias Explorer.Chain.{Block, Wei}
alias Explorer.ExchangeRates.Token
alias Explorer.{Market, Repo}
def market_cap(exchange_rate) do
circulating() * exchange_rate.usd_value
end
@doc "Equivalent to getting the circulating value "
def supply_for_days(days) do
now = Timex.now()
balances_query =
from(balance in CoinBalance,
join: block in Block,
on: block.number == balance.block_number,
where: block.consensus == true,
where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
where: block.timestamp > ^Timex.shift(now, days: -days),
distinct: fragment("date_trunc('day', ?)", block.timestamp),
select: {block.timestamp, balance.value}
)
balance_before_query =
from(balance in CoinBalance,
join: block in Block,
on: block.number == balance.block_number,
where: block.consensus == true,
where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
where: block.timestamp <= ^Timex.shift(Timex.now(), days: -days),
order_by: [desc: block.timestamp],
limit: 1,
select: balance.value
)
by_day =
balances_query
|> Repo.all()
|> Enum.into(%{}, fn {timestamp, value} ->
{Timex.to_date(timestamp), value}
end)
starting = Repo.one(balance_before_query) || wei!(0)
result =
-days..0
|> Enum.reduce({%{}, starting.value}, fn i, {days, last} ->
date =
now
|> Timex.shift(days: i)
|> Timex.to_date()
case Map.get(by_day, date) do
nil ->
{Map.put(days, date, last), last}
value ->
{Map.put(days, date, value.value), value.value}
end
end)
|> elem(0)
{:ok, result}
end
def circulating do
query =
from(balance in CoinBalance,
join: block in Block,
on: block.number == balance.block_number,
where: block.consensus == true,
where: balance.address_hash == ^"0x0000000000000000000000000000000001000006",
order_by: [desc: block.timestamp],
limit: 1,
select: balance.value
)
Repo.one(query) || wei!(0)
end
defp wei!(value) do
{:ok, wei} = Wei.cast(value)
wei
end
def total do
21_000_000
end
def exchange_rate do
Market.get_exchange_rate(Explorer.coin()) || Token.null()
end
end

@ -66,13 +66,23 @@ defmodule Explorer.Counters.AverageBlockTime do
from(block in Block,
limit: 100,
offset: 0,
order_by: [desc: block.number, desc: block.timestamp],
order_by: [desc: block.number],
select: {block.number, block.timestamp}
)
query =
if Application.get_env(:explorer, :include_uncles_in_average_block_time) do
timestamps_query
else
from(block in timestamps_query,
where: block.consensus == true
)
end
timestamps =
timestamps_query
query
|> Repo.all()
|> Enum.sort_by(fn {_, timestamp} -> timestamp end, &>=/2)
|> Enum.map(fn {number, timestamp} ->
{number, DateTime.to_unix(timestamp, :millisecond)}
end)

@ -3,12 +3,10 @@ defmodule Explorer.Market do
Context for data related to the cryptocurrency market.
"""
import Ecto.Query
alias Explorer.Chain.Address.CurrentTokenBalance
alias Explorer.Chain.Hash
alias Explorer.ExchangeRates.Token
alias Explorer.Market.MarketHistory
alias Explorer.Market.{MarketHistory, MarketHistoryCache}
alias Explorer.{ExchangeRates, KnownTokens, Repo}
@doc """
@ -35,18 +33,9 @@ defmodule Explorer.Market do
Today's date is include as part of the day count
"""
@spec fetch_recent_history(non_neg_integer()) :: [MarketHistory.t()]
def fetch_recent_history(days) when days >= 1 do
day_diff = days * -1
query =
from(
mh in MarketHistory,
where: mh.date > date_add(^Date.utc_today(), ^day_diff, "day"),
order_by: [desc: mh.date]
)
Repo.all(query)
@spec fetch_recent_history() :: [MarketHistory.t()]
def fetch_recent_history do
MarketHistoryCache.fetch()
end
@doc false

@ -0,0 +1,79 @@
defmodule Explorer.Market.MarketHistoryCache do
@moduledoc """
Caches recent market history.
"""
import Ecto.Query, only: [from: 2]
alias Explorer.Market.MarketHistory
alias Explorer.Repo
@cache_name :market_history
@last_update_key :last_update
@history_key :history
# 6 hours
@cache_period 1_000 * 60 * 60 * 6
@recent_days 30
def fetch do
if cache_expired?() do
update_cache()
else
fetch_from_cache(@history_key)
end
end
def cache_name, do: @cache_name
def data_key, do: @history_key
def updated_at_key, do: @last_update_key
def recent_days_count, do: @recent_days
defp cache_expired? do
updated_at = fetch_from_cache(@last_update_key)
cond do
is_nil(updated_at) -> true
current_time() - updated_at > @cache_period -> true
true -> false
end
end
defp update_cache do
new_data = fetch_from_db()
put_into_cache(@last_update_key, current_time())
put_into_cache(@history_key, new_data)
new_data
end
defp fetch_from_db do
day_diff = @recent_days * -1
query =
from(
mh in MarketHistory,
where: mh.date > date_add(^Date.utc_today(), ^day_diff, "day"),
order_by: [desc: mh.date]
)
Repo.all(query)
end
defp fetch_from_cache(key) do
ConCache.get(@cache_name, key)
end
defp put_into_cache(key, value) do
ConCache.put(@cache_name, key, value)
end
defp current_time do
utc_now = DateTime.utc_now()
DateTime.to_unix(utc_now, :millisecond)
end
end

@ -91,7 +91,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
"node",
[
Application.app_dir(:explorer, "priv/compile_solc.js"),
code,
create_source_file(code),
compiler_version,
optimize_value(optimize),
optimization_runs,
@ -162,4 +162,12 @@ defmodule Explorer.SmartContract.Solidity.CodeCompiler do
defp optimize_value(true), do: "1"
defp optimize_value("true"), do: "1"
defp create_source_file(source) do
{:ok, path} = Briefly.create()
File.write!(path, source)
path
end
end

@ -107,6 +107,13 @@ defmodule Explorer.SmartContract.Verifier do
|> Enum.reverse()
|> :binary.list_to_bin()
# Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst
"a265627a7a72305820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> _constructor_arguments ->
extracted
|> Enum.reverse()
|> :binary.list_to_bin()
<<next::binary-size(2)>> <> rest ->
do_extract_bytecode([next | extracted], rest)
end

@ -21,6 +21,14 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
constructor_arguments
end
# Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst
defp extract_constructor_arguments(
"a265627a7a72305820" <>
<<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments
) do
constructor_arguments
end
defp extract_constructor_arguments(<<_::binary-size(2)>> <> rest) do
extract_constructor_arguments(rest)
end

@ -66,6 +66,7 @@ defmodule Explorer.Mixfile do
# CSV output for benchee
{:benchee_csv, "~> 0.8.0", only: :test},
{:bypass, "~> 1.0", only: :test},
{:briefly, "~> 0.4", github: "CargoSense/briefly"},
{:comeonin, "~> 4.0"},
{:credo, "1.0.0", only: :test, runtime: false},
# For Absinthe to load data in batches
@ -92,6 +93,7 @@ defmodule Explorer.Mixfile do
{:mock, "~> 0.3.0", only: [:test], runtime: false},
{:mox, "~> 0.4", only: [:test]},
{:poison, "~> 3.1"},
{:nimble_csv, "~> 0.6.0"},
{:postgrex, ">= 0.0.0"},
# For compatibility with `prometheus_process_collector`, which hasn't been updated yet
{:prometheus, "~> 4.0", override: true},

@ -1,6 +1,6 @@
#!/usr/bin/env node
var sourceCode = process.argv[2];
var sourceCodePath = process.argv[2];
var version = process.argv[3];
var optimize = process.argv[4];
var optimizationRuns = parseInt(process.argv[5], 10);
@ -13,6 +13,9 @@ var solc = require('solc')
var compilerSnapshot = require(compilerVersionPath);
var solc = solc.setupMethods(compilerSnapshot);
var fs = require('fs');
var sourceCode = fs.readFileSync(sourceCodePath, 'utf8');
const input = {
language: 'Solidity',
sources: {

@ -0,0 +1,72 @@
defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
use Explorer.DataCase
alias Explorer.Chain.AddressTokenTransferCsvExporter
describe "export/1" do
test "exports token transfers to csv" do
address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
token_transfer = insert(:token_transfer, transaction: transaction, from_address: address)
[result] =
address
|> AddressTokenTransferCsvExporter.export()
|> Enum.to_list()
|> Enum.drop(1)
|> Enum.map(fn [
tx_hash,
_,
block_number,
_,
timestamp,
_,
from_address,
_,
to_address,
_,
token_contract_address,
_,
type,
_,
token_symbol,
_,
tokens_transferred,
_,
transaction_fee,
_,
status,
_,
err_code,
_
] ->
%{
tx_hash: tx_hash,
block_number: block_number,
timestamp: timestamp,
from_address: from_address,
to_address: to_address,
token_contract_address: token_contract_address,
type: type,
token_symbol: token_symbol,
tokens_transferred: tokens_transferred,
transaction_fee: transaction_fee,
status: status,
err_code: err_code
}
end)
assert result.block_number == to_string(transaction.block_number)
assert result.tx_hash == to_string(transaction.hash)
assert result.from_address == token_transfer.from_address_hash |> to_string() |> String.downcase()
assert result.to_address == token_transfer.to_address_hash |> to_string() |> String.downcase()
assert result.timestamp == to_string(transaction.block.timestamp)
assert result.type == "OUT"
end
end
end

@ -0,0 +1,105 @@
defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
use Explorer.DataCase
alias Explorer.Chain.{AddressTransactionCsvExporter, Wei}
describe "export/1" do
test "exports address transactions to csv" do
address = insert(:address)
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
|> Repo.preload(:token_transfers)
[result] =
address
|> AddressTransactionCsvExporter.export()
|> Enum.to_list()
|> Enum.drop(1)
|> Enum.map(fn [
hash,
_,
block_number,
_,
timestamp,
_,
from_address,
_,
to_address,
_,
created_address,
_,
type,
_,
value,
_,
fee,
_,
status,
_,
error,
_,
cur_price,
_,
op_price,
_,
cl_price,
_
] ->
%{
hash: hash,
block_number: block_number,
timestamp: timestamp,
from_address: from_address,
to_address: to_address,
created_address: created_address,
type: type,
value: value,
fee: fee,
status: status,
error: error,
current_price: cur_price,
opening_price: op_price,
closing_price: cl_price
}
end)
assert result.block_number == to_string(transaction.block_number)
assert result.timestamp
assert result.created_address == to_string(transaction.created_contract_address_hash)
assert result.from_address == to_string(transaction.from_address)
assert result.to_address == to_string(transaction.to_address)
assert result.hash == to_string(transaction.hash)
assert result.type == "OUT"
assert result.value == transaction.value |> Wei.to(:wei) |> to_string()
assert result.fee
assert result.status == to_string(transaction.status)
assert result.error == to_string(transaction.error)
assert result.current_price
assert result.opening_price
assert result.closing_price
end
test "fetches all transactions" do
address = insert(:address)
1..200
|> Enum.map(fn _ ->
:transaction
|> insert(from_address: address)
|> with_block()
end)
|> Enum.count()
result =
address
|> AddressTransactionCsvExporter.export()
|> Enum.to_list()
|> Enum.drop(1)
assert Enum.count(result) == 200
end
end
end

@ -0,0 +1,139 @@
defmodule Explorer.Chain.Supply.RSKTest do
use Explorer.DataCase
alias Explorer.Chain.Supply.RSK
alias Explorer.Chain.Wei
@coin_address "0x0000000000000000000000000000000001000006"
defp wei!(value) do
{:ok, wei} = Wei.cast(value)
wei
end
test "total is 21_000_000" do
assert RSK.total() == 21_000_000
end
describe "circulating/0" do
test "with no balance" do
assert RSK.circulating() == wei!(0)
end
test "with a balance" do
address = insert(:address, hash: @coin_address)
insert(:block, number: 0)
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
assert RSK.circulating() == wei!(10)
end
end
defp date(now, shift \\ []) do
now
|> Timex.shift(shift)
|> Timex.to_date()
end
defp dec(number) do
Decimal.new(number)
end
describe "supply_for_days/1" do
test "when there is no balance" do
now = Timex.now()
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(0),
date(now, days: -1) => dec(0),
date(now) => dec(0)
}}
end
test "when there is a single balance before the days, that balance is used" do
address = insert(:address, hash: @coin_address)
now = Timex.now()
insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(10),
date(now) => dec(10)
}}
end
test "when there is a balance for one of the days, days after it use that balance" do
address = insert(:address, hash: @coin_address)
now = Timex.now()
insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
insert(:block, number: 1, timestamp: Timex.shift(now, days: -1))
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 1)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(20),
date(now) => dec(20)
}}
end
test "when there is a balance for the first day, that balance is used" do
address = insert(:address, hash: @coin_address)
now = Timex.now()
insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
insert(:block, number: 1, timestamp: Timex.shift(now, days: -2))
insert(:block, number: 2, timestamp: Timex.shift(now, days: -1))
insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(20),
date(now) => dec(20)
}}
end
test "when there is a balance for all days, they are each used correctly" do
address = insert(:address, hash: @coin_address)
now = Timex.now()
insert(:block, number: 0, timestamp: Timex.shift(now, days: -10))
insert(:block, number: 1, timestamp: Timex.shift(now, days: -2))
insert(:block, number: 2, timestamp: Timex.shift(now, days: -1))
insert(:block, number: 3, timestamp: now)
insert(:fetched_balance, value: 5, address_hash: address.hash, block_number: 0)
insert(:fetched_balance, value: 10, address_hash: address.hash, block_number: 1)
insert(:fetched_balance, value: 20, address_hash: address.hash, block_number: 2)
insert(:fetched_balance, value: 30, address_hash: address.hash, block_number: 3)
assert RSK.supply_for_days(2) ==
{:ok,
%{
date(now, days: -2) => dec(10),
date(now, days: -1) => dec(20),
date(now) => dec(30)
}}
end
end
end

@ -3954,7 +3954,7 @@ defmodule Explorer.ChainTest do
insert(:token_transfer, from_address: from_address, to_address: to_address, transaction: transaction)
assert {:erc721, _found_token_transfer} = Chain.transaction_token_transfer_type(transaction)
assert :erc721 = Chain.transaction_token_transfer_type(Repo.preload(transaction, token_transfers: :token))
end
test "detects erc20 token transfer" do
@ -3980,7 +3980,7 @@ defmodule Explorer.ChainTest do
amount: 8_025_000_000_000_000_000_000
)
assert {:erc20, _found_token_transfer} = Chain.transaction_token_transfer_type(transaction)
assert :erc20 = Chain.transaction_token_transfer_type(Repo.preload(transaction, token_transfers: :token))
end
end

@ -11,6 +11,8 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
start_supervised!(AverageBlockTime)
Application.put_env(:explorer, AverageBlockTime, enabled: true)
Application.put_env(:explorer, :include_uncles_in_average_block_time, true)
on_exit(fn ->
Application.put_env(:explorer, AverageBlockTime, enabled: false)
end)
@ -43,6 +45,35 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT3S")
end
test "excludes uncles if include_uncles_in_average_block_time is set to false" do
block_number = 99_999_999
Application.put_env(:explorer, :include_uncles_in_average_block_time, false)
first_timestamp = Timex.now()
insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3))
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4))
insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5))
AverageBlockTime.refresh()
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT2S")
end
test "excludes uncles if include_uncles_in_average_block_time is set to true" do
block_number = 99_999_999
first_timestamp = Timex.now()
insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3))
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 4))
insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 5))
AverageBlockTime.refresh()
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT1S")
end
test "when there are no uncles sorts by block number" do
block_number = 99_999_999

@ -0,0 +1,90 @@
defmodule Explorer.Market.MarketHistoryCacheTest do
use Explorer.DataCase
alias Explorer.Market
alias Explorer.Market.MarketHistoryCache
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
on_exit(fn ->
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
end)
:ok
end
describe "fetch/1" do
test "caches data on the first call" do
today = Date.utc_today()
records =
for i <- 0..29 do
%{
date: Timex.shift(today, days: i * -1),
closing_price: Decimal.new(1),
opening_price: Decimal.new(1)
}
end
Market.bulk_insert_history(records)
refute fetch_data()
assert Enum.count(MarketHistoryCache.fetch()) == 30
assert fetch_data() == records
end
test "updates cache if cache is stale" do
today = Date.utc_today()
stale_records =
for i <- 0..29 do
%{
date: Timex.shift(today, days: i * -1),
closing_price: Decimal.new(1),
opening_price: Decimal.new(1)
}
end
Market.bulk_insert_history(stale_records)
MarketHistoryCache.fetch()
stale_updated_at = fetch_updated_at()
assert fetch_data() == stale_records
ConCache.put(MarketHistoryCache.cache_name(), MarketHistoryCache.updated_at_key(), 1)
fetch_data()
assert stale_updated_at != fetch_updated_at()
end
end
defp fetch_updated_at do
ConCache.get(MarketHistoryCache.cache_name(), MarketHistoryCache.updated_at_key())
end
defp fetch_data do
MarketHistoryCache.cache_name()
|> ConCache.get(MarketHistoryCache.data_key())
|> case do
nil ->
nil
records ->
Enum.map(records, fn record ->
%{
date: record.date,
closing_price: record.closing_price,
opening_price: record.opening_price
}
end)
end
end
end

@ -1,15 +1,27 @@
defmodule Explorer.MarketTest do
use Explorer.DataCase
use Explorer.DataCase, async: false
alias Explorer.Market
alias Explorer.Market.MarketHistory
alias Explorer.Repo
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
on_exit(fn ->
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
end)
:ok
end
test "fetch_recent_history/1" do
today = Date.utc_today()
records =
for i <- 0..5 do
for i <- 0..29 do
%{
date: Timex.shift(today, days: i * -1),
closing_price: Decimal.new(1),
@ -19,16 +31,9 @@ defmodule Explorer.MarketTest do
Market.bulk_insert_history(records)
history = Market.fetch_recent_history(1)
assert length(history) == 1
history = Market.fetch_recent_history()
assert length(history) == 30
assert Enum.at(history, 0).date == Enum.at(records, 0).date
more_history = Market.fetch_recent_history(5)
assert length(more_history) == 5
for {history_record, index} <- Enum.with_index(more_history) do
assert history_record.date == Enum.at(records, index).date
end
end
describe "bulk_insert_history/1" do

@ -268,6 +268,20 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
assert Enum.any?(abi, fn el -> el["type"] == "constructor" end)
end
test "can compile a large file" do
path = File.cwd!() <> "/test/support/fixture/smart_contract/large_smart_contract.sol"
contract = File.read!(path)
assert {:ok, %{"abi" => abi}} =
CodeCompiler.run(
name: "HomeWorkDeployer",
compiler_version: "v0.5.9+commit.e560f70d",
code: contract,
evm_version: "constantinople",
optimize: true
)
end
end
describe "get_contract_info/1" do

File diff suppressed because one or more lines are too long

@ -40,8 +40,8 @@ defmodule Explorer.DataCase do
end
Explorer.Chain.BlockNumberCache.setup()
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
:ok
end

@ -0,0 +1,76 @@
pragma solidity ^0.5.9;
contract Token {
function totalSupply() public view returns (uint256 supply) {}
function balanceOf(address _owner) public view returns (uint256 balance) {}
function transfer(address _to, uint256 _value) public returns (bool success) {}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {}
function approve(address _spender, uint256 _value) public returns (bool success) {}
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {}
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
contract StandardToken is Token {
function transfer(address _to, uint256 _value) public returns (bool success) {
//if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
if (balances[msg.sender] >= _value && _value > 0) {
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
} else { return false; }
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
//if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
} else { return false; }
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
return allowed[_owner][_spender];
}
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
uint256 totalTokenSupply;
}
contract TestToken is StandardToken {
/* Public variables */
string public name;
uint8 public decimals;
string public symbol;
string public version = '0.1';
constructor(
uint256 _initialAmount,
string memory _tokenName,
uint8 _decimalUnits,
string memory _tokenSymbol
) public {
balances[msg.sender] = _initialAmount;
totalTokenSupply = _initialAmount;
name = _tokenName;
decimals = _decimalUnits;
symbol = _tokenSymbol;
}
function approveAndCall(address _spender, uint256 _value) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
}

@ -31,7 +31,8 @@ block_transformer =
config :indexer,
block_transformer: block_transformer,
ecto_repos: [Explorer.Repo],
metadata_updater_days_interval: 2,
metadata_updater_seconds_interval:
String.to_integer(System.get_env("TOKEN_METADATA_UPDATE_INTERVAL") || "#{2 * 24 * 60 * 60}"),
# bytes
memory_limit: 1 <<< 30,
first_block: System.get_env("FIRST_BLOCK") || "0"

@ -93,7 +93,10 @@ defmodule Indexer.Fetcher.TokenBalance do
end
def fetch_from_blockchain(params_list) do
retryable_params_list = Enum.filter(params_list, &(&1.retries_count <= @max_retries))
retryable_params_list =
params_list
|> Enum.filter(&(&1.retries_count <= @max_retries))
|> Enum.uniq_by(&Map.take(&1, [:token_contract_address_hash, :address_hash, :block_number]))
Logger.metadata(count: Enum.count(retryable_params_list))

@ -29,7 +29,7 @@ defmodule Indexer.Fetcher.TokenUpdater do
|> Enum.reverse()
|> update_metadata()
Process.send_after(self(), :update_tokens, :timer.hours(state.update_interval) * 24)
Process.send_after(self(), :update_tokens, :timer.seconds(state.update_interval))
{:noreply, state}
end

@ -72,7 +72,7 @@ defmodule Indexer.Supervisor do
subscribe_named_arguments: subscribe_named_arguments
} = named_arguments
metadata_updater_inverval = Application.get_env(:indexer, :metadata_updater_days_interval)
metadata_updater_inverval = Application.get_env(:indexer, :metadata_updater_seconds_interval)
block_fetcher =
named_arguments

@ -49,7 +49,7 @@ defmodule Indexer.MixProject do
# JSONRPC access to Parity for `Explorer.Indexer`
{:ethereum_jsonrpc, in_umbrella: true},
# RLP encoding
{:ex_rlp, "~> 0.3"},
{:ex_rlp, "~> 0.5.2"},
# Code coverage
{:excoveralls, "~> 0.10.0", only: [:test], github: "KronicDeth/excoveralls", branch: "circle-workflows"},
# Importing to database

@ -111,6 +111,41 @@ defmodule Indexer.Fetcher.TokenBalanceTest do
assert TokenBalance.run(token_balances, nil) == :ok
end
test "fetches duplicate params only once" do
%Address.TokenBalance{
address_hash: %Hash{bytes: address_hash_bytes} = address_hash,
token_contract_address_hash: %Hash{bytes: token_contract_address_hash_bytes},
block_number: block_number
} = insert(:token_balance, value_fetched_at: nil, value: nil)
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: "eth_call", params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[
%{
id: id,
jsonrpc: "2.0",
result: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000"
}
]}
end
)
assert TokenBalance.run(
[
{address_hash_bytes, token_contract_address_hash_bytes, block_number, 0},
{address_hash_bytes, token_contract_address_hash_bytes, block_number, 0}
],
nil
) == :ok
assert 1 =
from(tb in Address.TokenBalance, where: tb.address_hash == ^address_hash)
|> Explorer.Repo.aggregate(:count, :id)
end
end
describe "import_token_balances/1" do

@ -1,6 +1,6 @@
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
export CHROMEDRIVER_VERSION=74.0.3729.6
export CHROMEDRIVER_VERSION=`curl -s http://chromedriver.storage.googleapis.com/LATEST_RELEASE`
curl -L -O "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip"
unzip chromedriver_linux64.zip
sudo chmod +x chromedriver

@ -3,7 +3,6 @@
- About BlockScout
- [About](about.md)
- [Projects Using BlockScout](projects.md)
- [Umbrella Project Organization](umbrella.md)
- Installation & Configuration
@ -23,6 +22,7 @@
- [Tracing](tracing.md)
- [Memory Usage](memory-usage.md)
- [API Docs](api.md)
- [Upgrading](upgrading.md)
- User Guide

@ -20,11 +20,11 @@ We would like to thank the [EthPrize foundation](http://ethprize.io/) for their
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments.
See [CONTRIBUTING.md](https://github.com/poanetwork/blockscout/blob/master/CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](https://github.com/poanetwork/blockscout/blob/master/CODE_OF_CONDUCT.md) when submitting code or comments.
## License
[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details.
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](https://github.com/poanetwork/blockscout/blob/master/LICENSE) file for details.

@ -4,7 +4,7 @@
We use [Ansible](https://docs.ansible.com/ansible/latest/index.html) & [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout.
The playbook repository is located at [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform). Currently it only supports [AWS](#AWS) as a cloud provider.
The playbook repository is located at [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform). Currently it only supports [AWS](#AWS-permissions) as a cloud provider.
In the root folder you will find Ansible Playbooks to create all necessary infrastructure to deploy BlockScout. The `lambda` folder also contains a set of scripts that may be useful in your BlockScout infrastructure.

@ -1,6 +1,6 @@
# BlockScout Env Variables
Below is a table outlining the environment variables utilized by BlockScout.
Below is a table outlining the environment variables utilized by BlockScout.
| Variable | Required | Description | Default | Version |

@ -1,3 +1,3 @@
<!-- faq.md -->
_Coming Soon_
FAQs are located in the [BlockScout forum](https://forum.poa.network/c/blockscout/wiki).

@ -6,12 +6,10 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">
<link rel="stylesheet" href="//unpkg.com/docsify/themes/buble.css">
<style>
:root {
/* Reduce the font size */
--base-font-size: 18px;
nav.app-nav li ul {
min-width: 100px;
}
</style>
@ -32,7 +30,6 @@
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docsify-themeable@0"></script>
<script src="https://unpkg.com/docsify-plugin-flexible-alerts"></script>
</body>
</html>

@ -1,20 +0,0 @@
<!-- projects.md -->
### Supported Projects
| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** |
|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------|
| [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) |
| [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) |
| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) |
| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) |
| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) |
| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) |
| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) |
| | | [SpringChain](https://explorer.springrole.com/) |
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) |
| | | [Tenda](https://tenda.network) |
Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).

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

Loading…
Cancel
Save