Merge branch 'master' into ab-hide-logs-search

pull/2247/head
Ayrat Badykov 5 years ago committed by GitHub
commit ce64bff0ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      CHANGELOG.md
  2. 4
      apps/block_scout_web/assets/css/app.scss
  3. 13
      apps/block_scout_web/assets/css/components/_navbar.scss
  4. 334
      apps/block_scout_web/assets/css/components/_network-selector.scss
  5. 49
      apps/block_scout_web/assets/css/components/_radio.scss
  6. 49
      apps/block_scout_web/assets/css/components/_transaction.scss
  7. 3
      apps/block_scout_web/assets/js/app.js
  8. 8
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  9. 77
      apps/block_scout_web/assets/js/lib/network_selector.js
  10. 58
      apps/block_scout_web/assets/js/pages/favorites.js
  11. 21
      apps/block_scout_web/assets/js/pages/network-search.js
  12. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/aerum-mainnet.png
  13. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/callisto-mainnet.png
  14. 7
      apps/block_scout_web/assets/static/images/network-selector-icons/callisto.svg
  15. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/core.svg
  16. 33
      apps/block_scout_web/assets/static/images/network-selector-icons/dai.svg
  17. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-classic.png
  18. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/ethereum-mainnet.png
  19. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/goerli-testnet.png
  20. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/kovan-testnet.png
  21. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/poa-core.png
  22. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/poa-sokol.png
  23. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby-testnet.png
  24. 15
      apps/block_scout_web/assets/static/images/network-selector-icons/rinkeby.svg
  25. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/ropsten-testnet.png
  26. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/ropsten.svg
  27. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/rsk-mainnet.png
  28. 4
      apps/block_scout_web/assets/static/images/network-selector-icons/sokol.svg
  29. BIN
      apps/block_scout_web/assets/static/images/network-selector-icons/xdai-chain.png
  30. 41
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  31. 4
      apps/block_scout_web/lib/block_scout_web/router.ex
  32. 4
      apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex
  33. 9
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  34. 10
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  35. 11
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  36. 53
      apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector.html.eex
  37. 25
      apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex
  38. 17
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  39. 9
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/index.html.eex
  40. 12
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  41. 10
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  42. 15
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  43. 121
      apps/block_scout_web/priv/gettext/default.pot
  44. 148
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  45. 36
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  46. 33
      apps/explorer/lib/explorer/chain.ex
  47. 119
      apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
  48. 139
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  49. 2
      apps/explorer/mix.exs
  50. 72
      apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs
  51. 105
      apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs
  52. 4
      apps/explorer/test/explorer/chain_test.exs
  53. 2
      apps/indexer/mix.exs
  54. 2
      bin/install_chrome_headless.sh
  55. 1
      mix.lock

@ -6,9 +6,15 @@
- [#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
### 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
@ -54,12 +60,14 @@
### 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.
@ -85,6 +93,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

@ -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";

@ -151,19 +151,13 @@ $navbar-logo-width: auto !default;
}
@include media-breakpoint-up(xl) {
width: 280px;
width: 340px;
}
@media (min-width: 1366px) {
width: 330px;
width: 500px;
}
@media (min-width: 1440px) {
width: 380px;
}
@media (min-width: 1580px) {
width: 430px;
}
@media (min-width: 1800px) {
width: 520px;
width: 580px;
}
}
.input-group-append {
@ -223,6 +217,7 @@ $navbar-logo-width: auto !default;
.navbar-brand {
margin-left: 0;
flex-shrink: 1;
display: inline-flex;
.navbar-logo {
max-width: 100%;
}

@ -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,8 @@ 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'
@ -57,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()
})

@ -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,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

@ -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

@ -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>

@ -68,7 +68,16 @@
<div data-items></div>
<!--<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>

@ -21,8 +21,18 @@
<div data-items></div>
<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>
</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;">
@ -65,7 +67,16 @@
<div data-items></div>
<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>

@ -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>

@ -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" %>

@ -28,7 +28,16 @@
<div data-items></div>
<!--<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>

@ -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>

@ -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 ""
@ -112,6 +112,7 @@ 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"
@ -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 ""
@ -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 ""
@ -690,7 +691,7 @@ 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 ""
@ -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 ""
@ -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 ""
@ -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,51 @@ 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_internal_transaction/index.html.eex:73
#: lib/block_scout_web/templates/address_token/index.html.eex:26
#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:33
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:20
#: 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 ""
@ -733,7 +734,7 @@ msgid "There are no token transfers for this address."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_token/index.html.eex:18
#: lib/block_scout_web/templates/address_token/index.html.eex:19
msgid "There are no tokens for this address."
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,13 +806,13 @@ 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 ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:8
#: lib/block_scout_web/templates/address_token/index.html.eex:8
#: lib/block_scout_web/templates/address_token/index.html.eex:9
#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9
#: lib/block_scout_web/views/address_view.ex:304
msgid "Tokens"
@ -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 ""
@ -1208,9 +1209,9 @@ msgstr ""
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61
#: 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/index.html.eex:14
#: 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, fuzzy
#: 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,52 @@ 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:10
msgid "Change Network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23
msgid "Favorites"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11
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:21
msgid "Mainnet"
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17
msgid "Search network"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22
msgid "Testnet"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_transaction/index.html.eex:74
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 ""

@ -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

@ -2923,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,
@ -2933,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
@ -2959,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}]
@ -2972,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

@ -92,7 +92,9 @@ defmodule Explorer.Mixfile do
{:math, "~> 0.3.0"},
{:mock, "~> 0.3.0", only: [:test], runtime: false},
{:mox, "~> 0.4", only: [:test]},
{:nimble_csv, "~> 0.6.0"},
{: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},

@ -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

@ -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

@ -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

@ -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

@ -74,6 +74,7 @@
"mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mox": {:hex, :mox, "0.4.0", "7f120840f7d626184a3d65de36189ca6f37d432e5d63acd80045198e4c5f7e6e", [:mix], [], "hexpm"},
"msgpax": {:hex, :msgpax, "2.2.2", "559a07806bbe5fdd31e4597455be030bd96356f7a621ca9eed832747c83bfb67", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"nimble_csv": {:hex, :nimble_csv, "0.6.0", "a3673f26d41f986774fe6060e309615343d3cb83a6d435754d8b1fdbd5764879", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm"},
"parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], []},

Loading…
Cancel
Save