Merge branch 'master' into master

pull/594/head
Mulili Nzuki 6 years ago committed by GitHub
commit 3bf915ee05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      .circleci/config.yml
  2. 13
      apps/block_scout_web/assets/__tests__/pages/block.js
  3. 29
      apps/block_scout_web/assets/css/components/_button.scss
  4. 2
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  5. 5
      apps/block_scout_web/assets/js/lib/token_balance_dropdown.js
  6. 60
      apps/block_scout_web/assets/js/pages/address.js
  7. 20
      apps/block_scout_web/assets/js/pages/block.js
  8. 18
      apps/block_scout_web/assets/js/pages/transaction.js
  9. 2
      apps/block_scout_web/assets/static/images/sokol_logo.svg
  10. 20
      apps/block_scout_web/lib/block_scout_web/chain.ex
  11. 28
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  12. 22
      apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
  13. 22
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  14. 2
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  15. 2
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  16. 1
      apps/block_scout_web/lib/block_scout_web/event_handler.ex
  17. 28
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  18. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex
  19. 8
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  20. 8
      apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
  21. 24
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  22. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  23. 8
      apps/block_scout_web/lib/block_scout_web/templates/api_docs/_action_tile.html.eex
  24. 2
      apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex
  25. 2
      apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex
  26. 4
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  27. 2
      apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
  28. 2
      apps/block_scout_web/lib/block_scout_web/templates/smart_contract/_functions.html.eex
  29. 34
      apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
  30. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/token/show.html.eex
  31. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex
  32. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
  33. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex
  34. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex
  35. 4
      apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex
  36. 37
      apps/block_scout_web/priv/gettext/default.pot
  37. 37
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  38. 18
      apps/block_scout_web/test/block_scout_web/chain_test.exs
  39. 70
      apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs
  40. 60
      apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs
  41. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs
  42. 8
      apps/block_scout_web/test/block_scout_web/controllers/address_token_balance_controller_test.exs
  43. 73
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  44. 23
      apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs
  45. 2
      apps/block_scout_web/test/block_scout_web/features/pages/address_contract_page.ex
  46. 16
      apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex
  47. 12
      apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex
  48. 2
      apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex
  49. 4
      apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex
  50. 4
      apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex
  51. 25
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  52. 29
      apps/block_scout_web/test/block_scout_web/views/block_view_test.exs
  53. 2
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  54. 19
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs
  55. 5
      apps/explorer/lib/explorer/chain.ex
  56. 7
      apps/explorer/lib/explorer/chain/import.ex
  57. 4
      apps/explorer/lib/explorer/etherscan.ex
  58. 6
      apps/explorer/test/explorer/chain/import_test.exs
  59. 33
      apps/explorer/test/explorer/etherscan_test.exs
  60. 11
      apps/explorer/test/support/factory.ex
  61. 34
      apps/indexer/lib/indexer/token_fetcher.ex
  62. 8
      apps/indexer/test/indexer/block_fetcher/realtime_test.exs
  63. 116
      apps/indexer/test/indexer/token_fetcher_test.exs

@ -338,8 +338,8 @@ jobs:
mix coveralls.html --exclude no_geth --parallel --umbrella mix coveralls.html --exclude no_geth --parallel --umbrella
else else
mix coveralls.circle --exclude no_geth --parallel --umbrella || mix coveralls.circle --exclude no_geth --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so single done here # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
fi fi
- store_artifacts: - store_artifacts:
@ -388,8 +388,8 @@ jobs:
mix coveralls.html --exclude no_geth --parallel --umbrella mix coveralls.html --exclude no_geth --parallel --umbrella
else else
mix coveralls.circle --exclude no_geth --parallel --umbrella || mix coveralls.circle --exclude no_geth --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so signal done here # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
fi fi
- store_artifacts: - store_artifacts:
@ -438,8 +438,8 @@ jobs:
mix coveralls.html --exclude no_parity --parallel --umbrella mix coveralls.html --exclude no_parity --parallel --umbrella
else else
mix coveralls.circle --exclude no_parity --parallel --umbrella || mix coveralls.circle --exclude no_parity --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so signal done here # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
fi fi
- store_artifacts: - store_artifacts:
@ -488,8 +488,8 @@ jobs:
mix coveralls.html --exclude no_parity --parallel --umbrella mix coveralls.html --exclude no_parity --parallel --umbrella
else else
mix coveralls.circle --exclude no_parity --parallel --umbrella || mix coveralls.circle --exclude no_parity --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so single done here # if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done" && return $retval)
fi fi
- store_artifacts: - store_artifacts:

@ -0,0 +1,13 @@
import { reducer, initialState } from '../../js/pages/block'
test('RECEIVED_NEW_BLOCK', () => {
const action = {
type: 'RECEIVED_NEW_BLOCK',
msg: {
blockHtml: "test"
}
}
const output = reducer(initialState, action)
expect(output.newBlock).toBe("test")
})

@ -10,15 +10,15 @@
-webkit-transition: all 0.25s; -webkit-transition: all 0.25s;
transition: all 0.25s; transition: all 0.25s;
&--primary { &-primary {
background-color: $primary; background-color: $primary;
color: $white; color: $white;
border: 1px solid $primary; border: 1px solid $primary;
&:hover, &:hover,
&:focus { &:focus {
background-color: $primary; background-color: darken($primary, 10%);
border-color: $primary; border-color: darken($primary, 10%);
text-decoration: none; text-decoration: none;
} }
@ -29,50 +29,51 @@
} }
} }
&--secondary { &-secondary {
border: 1px solid $tertiary; border: 1px solid $tertiary;
color: $tertiary; color: $tertiary;
font-weight: 400; font-weight: 400;
&:hover, &:hover,
&:focus { &:focus {
background-color: darken($tertiary, 20%); background-color: darken($tertiary, 10%);
border-color: darken($tertiary, 20%); border-color: darken($tertiary, 10%);
color: $white; color: $white;
text-decoration: none; text-decoration: none;
outline: none !important; outline: none !important;
} }
} }
&--xsmall { &-xs {
font-size: 11px; font-size: 11px;
padding: 6px 9px 6px !important; padding: 6px 9px 6px !important;
} }
&--small { &-sm {
font-size: 12px; font-size: 12px;
padding: 10px 20px 10px; padding: 10px 20px 10px;
} }
&--medium { &-md {
font-size: 1.5rem; font-size: 1.5rem;
padding: 15px 30px 15px; padding: 15px 30px 15px;
} }
&--large { &-lg {
font-size: 1.5rem; font-size: 1.5rem;
padding: 20px 60px 20px; padding: 20px 60px 20px;
} }
// Block button // Block button
// -------------------------------------------------- // -------------------------
&--block { &-block {
display: block; display: block;
width: 100%; width: 100%;
} }
&--disabled { &-disabled,
&:disabled, {
background-color: $gray-300; background-color: $gray-300;
color: $gray-500; color: $gray-500;
text-decoration: none; text-decoration: none;
@ -81,6 +82,6 @@
} }
// Vertically space out multiple block buttons // Vertically space out multiple block buttons
.button--block + .button--block { .button-block + .button-block {
margin-top: 5px; margin-top: 5px;
} }

@ -31,7 +31,7 @@ $grays: map-merge((
$blue: #4786ff !default; $blue: #4786ff !default;
$indigo: #5b33a1 !default; $indigo: #5b33a1 !default;
$purple: #9987fc !default; $purple: #997fdc !default;
$pink: #e83e8c !default; $pink: #e83e8c !default;
$red: #c74d39 !default; $red: #c74d39 !default;
$orange: #ef9a60 !default; $orange: #ef9a60 !default;

@ -16,4 +16,7 @@ const tokenBalanceDropdown = (element) => {
}) })
} }
$('[data-token-balance-dropdown]').each((_index, element) => tokenBalanceDropdown(element)) export function loadTokenBalanceDropdown () {
$('[data-token-balance-dropdown]').each((_index, element) => tokenBalanceDropdown(element))
}
loadTokenBalanceDropdown()

@ -5,6 +5,7 @@ import socket from '../socket'
import router from '../router' import router from '../router'
import { batchChannel, initRedux } from '../utils' import { batchChannel, initRedux } from '../utils'
import { updateAllAges } from '../lib/from_now' import { updateAllAges } from '../lib/from_now'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
const BATCH_THRESHOLD = 10 const BATCH_THRESHOLD = 10
@ -14,6 +15,7 @@ export const initialState = {
beyondPageOne: null, beyondPageOne: null,
channelDisconnected: false, channelDisconnected: false,
filter: null, filter: null,
newInternalTransactions: [],
newTransactions: [], newTransactions: [],
balance: null, balance: null,
transactionCount: null transactionCount: null
@ -42,6 +44,29 @@ export function reducer (state = initialState, action) {
balance: action.msg.balance balance: action.msg.balance
}) })
} }
case 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH': {
if (state.channelDisconnected || state.beyondPageOne) return state
const incomingInternalTransactions = humps.camelizeKeys(action.msgs)
.filter(({toAddressHash, fromAddressHash}) => (
!state.filter ||
(state.filter === 'to' && toAddressHash === state.addressHash) ||
(state.filter === 'from' && fromAddressHash === state.addressHash)
))
if (!state.batchCountAccumulator && action.msgs.length < BATCH_THRESHOLD) {
return Object.assign({}, state, {
newInternalTransactions: [
...state.newInternalTransactions,
...incomingInternalTransactions.map(({internalTransactionHtml}) => internalTransactionHtml)
]
})
} else {
return Object.assign({}, state, {
batchCountAccumulator: state.batchCountAccumulator + action.msgs.length
})
}
}
case 'RECEIVED_NEW_TRANSACTION_BATCH': { case 'RECEIVED_NEW_TRANSACTION_BATCH': {
if (state.channelDisconnected || state.beyondPageOne) return state if (state.channelDisconnected || state.beyondPageOne) return state
@ -74,30 +99,44 @@ export function reducer (state = initialState, action) {
router.when('/address/:addressHash').then((params) => initRedux(reducer, { router.when('/address/:addressHash').then((params) => initRedux(reducer, {
main (store) { main (store) {
const { addressHash, blockNumber } = params const { addressHash } = params
const channel = socket.channel(`addresses:${addressHash}`, {}) const addressChannel = socket.channel(`addresses:${addressHash}`, {})
store.dispatch({ const state = store.dispatch({
type: 'PAGE_LOAD', type: 'PAGE_LOAD',
params, params,
transactionCount: $('[data-selector="transaction-count"]').text() transactionCount: $('[data-selector="transaction-count"]').text()
}) })
channel.join() addressChannel.join()
channel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
channel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg })) addressChannel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg }))
if (!blockNumber) channel.on('transaction', batchChannel((msgs) => store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs }))) if (!state.beyondPageOne) {
addressChannel.on('transaction', batchChannel((msgs) =>
store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs })
))
addressChannel.on('internal_transaction', batchChannel((msgs) =>
store.dispatch({ type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', msgs })
))
}
}, },
render (state, oldState) { render (state, oldState) {
const $balance = $('[data-selector="balance-card"]')
const $channelBatching = $('[data-selector="channel-batching-message"]') const $channelBatching = $('[data-selector="channel-batching-message"]')
const $channelBatchingCount = $('[data-selector="channel-batching-count"]') const $channelBatchingCount = $('[data-selector="channel-batching-count"]')
const $channelDisconnected = $('[data-selector="channel-disconnected-message"]') const $channelDisconnected = $('[data-selector="channel-disconnected-message"]')
const $emptyInternalTransactionsList = $('[data-selector="empty-internal-transactions-list"]')
const $emptyTransactionsList = $('[data-selector="empty-transactions-list"]') const $emptyTransactionsList = $('[data-selector="empty-transactions-list"]')
const $balance = $('[data-selector="balance-card"]') const $internalTransactionsList = $('[data-selector="internal-transactions-list"]')
const $transactionCount = $('[data-selector="transaction-count"]') const $transactionCount = $('[data-selector="transaction-count"]')
const $transactionsList = $('[data-selector="transactions-list"]') const $transactionsList = $('[data-selector="transactions-list"]')
if ($emptyInternalTransactionsList.length && state.newInternalTransactions.length) window.location.reload()
if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload() if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload()
if (state.channelDisconnected) $channelDisconnected.show() if (state.channelDisconnected) $channelDisconnected.show()
if (oldState.balance !== state.balance) $balance.empty().append(state.balance) if (oldState.balance !== state.balance) {
$balance.empty().append(state.balance)
loadTokenBalanceDropdown()
}
if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format()) if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format())
if (state.batchCountAccumulator) { if (state.batchCountAccumulator) {
$channelBatching.show() $channelBatching.show()
@ -105,6 +144,9 @@ router.when('/address/:addressHash').then((params) => initRedux(reducer, {
} else { } else {
$channelBatching.hide() $channelBatching.hide()
} }
if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) {
$internalTransactionsList.prepend(state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join(''))
}
if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) { if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) {
$transactionsList.prepend(state.newTransactions.slice(oldState.newTransactions.length).reverse().join('')) $transactionsList.prepend(state.newTransactions.slice(oldState.newTransactions.length).reverse().join(''))
updateAllAges() updateAllAges()

@ -19,14 +19,12 @@ export function reducer (state = initialState, action) {
}) })
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
return Object.assign({}, state, { return Object.assign({}, state, {
channelDisconnected: true channelDisconnected: true
}) })
} }
case 'RECEIVED_NEW_BLOCK': { case 'RECEIVED_NEW_BLOCK': {
if (state.channelDisconnected || state.beyondPageOne) return state if (state.channelDisconnected) return state
return Object.assign({}, state, { return Object.assign({}, state, {
newBlock: action.msg.blockHtml newBlock: action.msg.blockHtml
@ -39,13 +37,15 @@ export function reducer (state = initialState, action) {
router.when('/blocks', { exactPathMatch: true }).then(({ blockNumber }) => initRedux(reducer, { router.when('/blocks', { exactPathMatch: true }).then(({ blockNumber }) => initRedux(reducer, {
main (store) { main (store) {
const blocksChannel = socket.channel(`blocks:new_block`, {}) const state = store.dispatch({ type: 'PAGE_LOAD', blockNumber })
store.dispatch({ type: 'PAGE_LOAD', blockNumber }) if (!state.beyondPageOne) {
blocksChannel.join() const blocksChannel = socket.channel(`blocks:new_block`, {})
blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) blocksChannel.join()
blocksChannel.on('new_block', (msg) => blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) }) blocksChannel.on('new_block', (msg) =>
) store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) })
)
}
}, },
render (state, oldState) { render (state, oldState) {
const $channelDisconnected = $('[data-selector="channel-disconnected-message"]') const $channelDisconnected = $('[data-selector="channel-disconnected-message"]')

@ -28,8 +28,6 @@ export function reducer (state = initialState, action) {
}) })
} }
case 'CHANNEL_DISCONNECTED': { case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
return Object.assign({}, state, { return Object.assign({}, state, {
channelDisconnected: true, channelDisconnected: true,
batchCountAccumulator: 0 batchCountAccumulator: 0
@ -88,17 +86,19 @@ router.when('/tx/:transactionHash').then(() => initRedux(reducer, {
router.when('/txs', { exactPathMatch: true }).then((params) => initRedux(reducer, { router.when('/txs', { exactPathMatch: true }).then((params) => initRedux(reducer, {
main (store) { main (store) {
const { index } = params const { index } = params
const transactionsChannel = socket.channel(`transactions:new_transaction`) const state = store.dispatch({
store.dispatch({
type: 'PAGE_LOAD', type: 'PAGE_LOAD',
transactionCount: $('[data-selector="transaction-count"]').text(), transactionCount: $('[data-selector="transaction-count"]').text(),
index index
}) })
transactionsChannel.join() if (!state.beyondPageOne) {
transactionsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) const transactionsChannel = socket.channel(`transactions:new_transaction`)
transactionsChannel.on('new_transaction', batchChannel((msgs) => transactionsChannel.join()
store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) })) transactionsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
) transactionsChannel.on('new_transaction', batchChannel((msgs) =>
store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }))
)
}
}, },
render (state, oldState) { render (state, oldState) {
const $channelBatching = $('[data-selector="channel-batching-message"]') const $channelBatching = $('[data-selector="channel-batching-message"]')

@ -1 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 331.518 63.14"><defs><style>.cls-1{font-size:56px;font-family:Nunito-ExtraLight, Nunito;letter-spacing:0.256em;}.cls-1,.cls-3{fill:#fff;}.cls-2{letter-spacing:0.245em;}</style></defs><title>sokol_logo</title><text class="cls-1" transform="translate(135.687 47.74)">so<tspan class="cls-2" x="85.344" y="0">k</tspan><tspan x="125.271" y="0">ol</tspan></text><path class="cls-3" d="M128.7,31.492H40.539a1.825,1.825,0,0,0-1.824,1.825V57.57H82.931a1.216,1.216,0,0,1,1.18.922l4,16.016h50.424a1.825,1.825,0,0,0,1.77-2.267l-9.842-39.367A1.824,1.824,0,0,0,128.7,31.492ZM52.266,48.507l-2.384-9.538a.607.607,0,0,1,.589-.755h9.5a.606.606,0,0,1,.589.46l2.385,9.538a.607.607,0,0,1-.59.755h-9.5A.608.608,0,0,1,52.266,48.507Zm18.819,0L68.7,38.969a.607.607,0,0,1,.589-.755h9.5a.606.606,0,0,1,.589.46l2.385,9.538a.607.607,0,0,1-.59.755h-9.5A.609.609,0,0,1,71.085,48.507Zm31.587-10.293h20.254a.608.608,0,0,1,.59.46l5.073,20.292a.608.608,0,0,1-.59.755H107.745a.608.608,0,0,1-.59-.461l-5.073-20.291A.607.607,0,0,1,102.672,38.214Zm6.774,30.725-.771-3.085a.608.608,0,0,1,.59-.756h3.048a.608.608,0,0,1,.59.461l.771,3.085a.607.607,0,0,1-.59.755h-3.048A.608.608,0,0,1,109.446,68.939ZM126.868,65.1h3.048a.608.608,0,0,1,.59.461l.771,3.085a.607.607,0,0,1-.59.755h-3.048a.608.608,0,0,1-.59-.46l-.771-3.085A.608.608,0,0,1,126.868,65.1Z" transform="translate(-38.715 -23.604)"/><path class="cls-3" d="M38.715,65.636v7.047a1.825,1.825,0,0,0,1.824,1.825H80.722L78.5,65.636Z" transform="translate(-38.715 -23.604)"/></svg> <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406.5 106"><defs><style>.cls-1{fill:#fff;}</style></defs><title>sokol_logo</title><path class="cls-1" d="M382.031,72.458a2.025,2.025,0,0,1-1.441.536,2.106,2.106,0,0,1-1.507-.536,2.014,2.014,0,0,1-.57-1.541V27.17a2.013,2.013,0,0,1,.57-1.541,2.106,2.106,0,0,1,1.507-.536,1.905,1.905,0,0,1,2.01,2.077V70.917A2.021,2.021,0,0,1,382.031,72.458Zm-32.663-1.407a15.894,15.894,0,0,1-15.711,0,13.931,13.931,0,0,1-5.259-5.9,19.956,19.956,0,0,1-1.876-8.91,19.962,19.962,0,0,1,1.876-8.91,13.923,13.923,0,0,1,5.259-5.9,15.894,15.894,0,0,1,15.711,0,13.649,13.649,0,0,1,5.259,5.9,20.278,20.278,0,0,1,1.844,8.91,20.272,20.272,0,0,1-1.844,8.91A13.657,13.657,0,0,1,349.368,71.051Zm.034-24.62a10.606,10.606,0,0,0-15.779,0q-2.814,3.516-2.813,9.814,0,6.432,2.746,9.848t7.907,3.417a9.745,9.745,0,0,0,7.972-3.417q2.814-3.416,2.814-9.848Q352.249,49.948,349.4,46.431ZM307.158,72.994a2.264,2.264,0,0,1-1.407-.6L287.527,56.848V70.917a1.905,1.905,0,0,1-2.009,2.077,2.112,2.112,0,0,1-1.508-.536,2.017,2.017,0,0,1-.569-1.541V27.17a2.016,2.016,0,0,1,.569-1.541,2.112,2.112,0,0,1,1.508-.536,1.905,1.905,0,0,1,2.009,2.077v28L304.21,40.167a2.266,2.266,0,0,1,1.407-.6,1.944,1.944,0,0,1,1.943,1.943,1.908,1.908,0,0,1-.8,1.607l-14.471,12.8L308.1,69.443a2.307,2.307,0,0,1,.871,1.675,1.856,1.856,0,0,1-.536,1.306A1.7,1.7,0,0,1,307.158,72.994ZM254.3,71.051a15.9,15.9,0,0,1-15.712,0,13.951,13.951,0,0,1-5.259-5.9,19.983,19.983,0,0,1-1.876-8.91,19.989,19.989,0,0,1,1.876-8.91,13.942,13.942,0,0,1,5.259-5.9,15.9,15.9,0,0,1,15.712,0,13.67,13.67,0,0,1,5.26,5.9,20.306,20.306,0,0,1,1.842,8.91,20.3,20.3,0,0,1-1.842,8.91A13.678,13.678,0,0,1,254.3,71.051Zm.033-24.62a10.6,10.6,0,0,0-15.778,0q-2.814,3.516-2.814,9.814,0,6.432,2.747,9.848t7.906,3.417a9.747,9.747,0,0,0,7.973-3.417q2.813-3.416,2.813-9.848Q257.176,49.948,254.329,46.431Zm-59.763,6.632a27.51,27.51,0,0,0,4.924,1.507,35.038,35.038,0,0,1,7.1,2.278,8.468,8.468,0,0,1,3.552,2.881,8.385,8.385,0,0,1-2.378,10.886q-3.453,2.513-9.146,2.513a20.2,20.2,0,0,1-5.527-.771,17.7,17.7,0,0,1-4.792-2.11,8.313,8.313,0,0,1-1.775-1.507,2.267,2.267,0,0,1-.5-1.441,1.612,1.612,0,0,1,.436-1.139,1.37,1.37,0,0,1,1.038-.469,4.79,4.79,0,0,1,2.077,1,21.855,21.855,0,0,0,3.987,2.077,14.108,14.108,0,0,0,5.126.8,11.236,11.236,0,0,0,6.163-1.44,4.632,4.632,0,0,0,2.212-4.12,4.194,4.194,0,0,0-.805-2.647,6.387,6.387,0,0,0-2.68-1.809,32.6,32.6,0,0,0-5.359-1.507q-5.965-1.34-8.477-3.45a7.017,7.017,0,0,1-2.512-5.661,8.416,8.416,0,0,1,3.35-6.934,13.634,13.634,0,0,1,8.71-2.646,18.3,18.3,0,0,1,4.991.67,12.791,12.791,0,0,1,4.122,1.943q2.343,1.606,2.345,3.148a1.742,1.742,0,0,1-.436,1.173,1.331,1.331,0,0,1-1.038.5,4.255,4.255,0,0,1-2.077-1.072,20.422,20.422,0,0,0-3.585-2.043,11.424,11.424,0,0,0-4.523-.771,8.975,8.975,0,0,0-5.56,1.575,5.054,5.054,0,0,0-2.077,4.254,4.338,4.338,0,0,0,.7,2.546A5.743,5.743,0,0,0,194.566,53.063ZM138.731,79.4h-40.5a7.02,7.02,0,0,1-1.644-.208l.056.208H79.168L73.922,59.86H20.807v-.287a3.157,3.157,0,0,1-2.792-3.116V32.687h0a6.344,6.344,0,0,1,6.345-6.324l.022,0v0H124.47a8.883,8.883,0,0,1,8.056,6.333l10.856,40.372C144.322,76.565,142.24,79.4,138.731,79.4ZM43.477,36.125H29.167L33,50.111H47.312Zm23.738,0H52.905l3.837,13.986H71.051Zm39.107,36.28h6.593l-1.771-6.94H104.55Zm17.343-36.28H96.182l7.383,26.521h27.483Zm8.434,29.34h-6.594l1.772,6.94h6.593ZM20.807,65.443H64.074v.021h4.914L72.456,79.4H24.994v-.032a6.338,6.338,0,0,1-6.929-5.551h-.05V69.63h.39a2.724,2.724,0,0,1-.39-1.4A2.79,2.79,0,0,1,20.807,65.443Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -18,6 +18,26 @@ defmodule BlockScoutWeb.Chain do
@page_size 50 @page_size 50
@default_paging_options %PagingOptions{page_size: @page_size + 1} @default_paging_options %PagingOptions{page_size: @page_size + 1}
def current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to, paging_options: paging_options]
"from" -> [direction: :from, paging_options: paging_options]
_ -> [paging_options: paging_options]
end
end
def current_filter(params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to]
"from" -> [direction: :from]
_ -> []
end
end
@spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found} @spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found}
def from_param(param) def from_param(param)

@ -4,10 +4,10 @@ defmodule BlockScoutWeb.AddressChannel do
""" """
use BlockScoutWeb, :channel use BlockScoutWeb, :channel
alias BlockScoutWeb.{AddressTransactionView, AddressView} alias BlockScoutWeb.{AddressInternalTransactionView, AddressView, TransactionView}
alias Phoenix.View alias Phoenix.View
intercept(["balance_update", "count", "transaction"]) intercept(["balance_update", "count", "internal_transaction", "transaction"])
def join("addresses:" <> _address_hash, _params, socket) do def join("addresses:" <> _address_hash, _params, socket) do
{:ok, %{}, socket} {:ok, %{}, socket}
@ -40,13 +40,33 @@ defmodule BlockScoutWeb.AddressChannel do
{:noreply, socket} {:noreply, socket}
end end
def handle_out("internal_transaction", %{address: address, internal_transaction: internal_transaction}, socket) do
Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale)
rendered_internal_transaction =
View.render_to_string(
AddressInternalTransactionView,
"_internal_transaction.html",
address: address,
internal_transaction: internal_transaction
)
push(socket, "internal_transaction", %{
to_address_hash: to_string(internal_transaction.to_address_hash),
from_address_hash: to_string(internal_transaction.from_address_hash),
internal_transaction_html: rendered_internal_transaction
})
{:noreply, socket}
end
def handle_out("transaction", %{address: address, transaction: transaction}, socket) do def handle_out("transaction", %{address: address, transaction: transaction}, socket) do
Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale)
rendered = rendered =
View.render_to_string( View.render_to_string(
AddressTransactionView, TransactionView,
"_transaction.html", "_tile.html",
address: address, address: address,
transaction: transaction transaction: transaction
) )

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1] import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
@ -46,24 +46,4 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
not_found(conn) not_found(conn)
end end
end end
defp current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to, paging_options: paging_options]
"from" -> [direction: :from, paging_options: paging_options]
_ -> [paging_options: paging_options]
end
end
defp current_filter(params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to]
"from" -> [direction: :from]
_ -> []
end
end
end end

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
use BlockScoutWeb, :controller use BlockScoutWeb, :controller
import BlockScoutWeb.AddressController, only: [transaction_count: 1] import BlockScoutWeb.AddressController, only: [transaction_count: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.{Chain, Market} alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
@ -46,24 +46,4 @@ defmodule BlockScoutWeb.AddressTransactionController do
not_found(conn) not_found(conn)
end end
end end
defp current_filter(%{paging_options: paging_options} = params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to, paging_options: paging_options]
"from" -> [direction: :from, paging_options: paging_options]
_ -> [paging_options: paging_options]
end
end
defp current_filter(params) do
params
|> Map.get("filter")
|> case do
"to" -> [direction: :to]
"from" -> [direction: :from]
_ -> []
end
end
end end

@ -322,7 +322,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
defp put_filter_by(options, params) do defp put_filter_by(options, params) do
case params do case params do
%{"filterby" => filter_by} when filter_by in ["to"] -> %{"filterby" => filter_by} when filter_by in ["from", "to"] ->
Map.put(options, :filter_by, filter_by) Map.put(options, :filter_by, filter_by)
_ -> _ ->

@ -661,7 +661,7 @@ defmodule BlockScoutWeb.Etherscan do
description: """ description: """
A string representing the field to filter by. If none is given A string representing the field to filter by. If none is given
it returns transactions that match to, from, or contract address. it returns transactions that match to, from, or contract address.
Available values: to Available values: to, from
""" """
} }
], ],

@ -19,6 +19,7 @@ defmodule BlockScoutWeb.EventHandler do
Chain.subscribe_to_events(:addresses) Chain.subscribe_to_events(:addresses)
Chain.subscribe_to_events(:blocks) Chain.subscribe_to_events(:blocks)
Chain.subscribe_to_events(:exchange_rate) Chain.subscribe_to_events(:exchange_rate)
Chain.subscribe_to_events(:internal_transactions)
Chain.subscribe_to_events(:transactions) Chain.subscribe_to_events(:transactions)
{:ok, []} {:ok, []}
end end

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Notifier do
""" """
alias Explorer.{Chain, Market, Repo} alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.Address alias Explorer.Chain.{Address, InternalTransaction}
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias BlockScoutWeb.Endpoint alias BlockScoutWeb.Endpoint
@ -36,6 +36,12 @@ defmodule BlockScoutWeb.Notifier do
}) })
end end
def handle_event({:chain_event, :internal_transactions, internal_transactions}) do
internal_transactions
|> Stream.map(&(InternalTransaction |> Repo.get(&1.id) |> Repo.preload([:from_address, :to_address])))
|> Enum.each(&broadcast_internal_transaction/1)
end
def handle_event({:chain_event, :transactions, transaction_hashes}) do def handle_event({:chain_event, :transactions, transaction_hashes}) do
transaction_hashes transaction_hashes
|> Chain.hashes_to_transactions( |> Chain.hashes_to_transactions(
@ -65,6 +71,24 @@ defmodule BlockScoutWeb.Notifier do
}) })
end end
defp broadcast_internal_transaction(internal_transaction) do
Endpoint.broadcast("internal_transactions:new_internal_transaction", "new_internal_transaction", %{
internal_transaction: internal_transaction
})
Endpoint.broadcast("addresses:#{internal_transaction.from_address_hash}", "internal_transaction", %{
address: internal_transaction.from_address,
internal_transaction: internal_transaction
})
if internal_transaction.to_address_hash != internal_transaction.from_address_hash do
Endpoint.broadcast("addresses:#{internal_transaction.to_address_hash}", "internal_transaction", %{
address: internal_transaction.to_address,
internal_transaction: internal_transaction
})
end
end
defp broadcast_transaction(transaction) do defp broadcast_transaction(transaction) do
Endpoint.broadcast("transactions:new_transaction", "new_transaction", %{ Endpoint.broadcast("transactions:new_transaction", "new_transaction", %{
transaction: transaction transaction: transaction
@ -75,7 +99,7 @@ defmodule BlockScoutWeb.Notifier do
transaction: transaction transaction: transaction
}) })
if transaction.to_address_hash && transaction.to_address_hash != transaction.from_address_hash do if transaction.to_address_hash != transaction.from_address_hash do
Endpoint.broadcast("addresses:#{transaction.to_address_hash}", "transaction", %{ Endpoint.broadcast("addresses:#{transaction.to_address_hash}", "transaction", %{
address: transaction.to_address, address: transaction.to_address,
transaction: transaction transaction: transaction

@ -9,7 +9,7 @@
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"> data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
</span> </span>
<div class="mt-3" data-token-balance-dropdown data-api_path="<%= address_token_balance_path(@conn, :index, @address.hash) %>"> <div class="mt-3" data-token-balance-dropdown data-api_path="<%= address_token_balance_path(BlockScoutWeb.Endpoint, :index, @address.hash) %>">
<p data-loading class="mb-0 text-light" style="display: none;"> <p data-loading class="mb-0 text-light" style="display: none;">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin"></i>
<%= gettext("Fetching tokens...") %> <%= gettext("Fetching tokens...") %>

@ -47,7 +47,7 @@
<%= link( <%= link(
gettext("Verify and Publish"), gettext("Verify and Publish"),
to: address_verify_contract_path(@conn, :new, @conn.params["address_id"]), to: address_verify_contract_path(@conn, :new, @conn.params["address_id"]),
class: "button button--primary button--sm float-right ml-3", class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish" "data-test": "verify_and_publish"
) %> ) %>
<% end %> <% end %>
@ -72,7 +72,7 @@
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3>Contract source code</h3> <h3>Contract source code</h3>
<span class="icon-links" data-clipboard-text="<%= @address.smart_contract.contract_source_code %>"> <span class="icon-links" data-clipboard-text="<%= @address.smart_contract.contract_source_code %>">
<button type="button" class="button button--secondary button--sm" id="button" data-toggle="tooltip" data-placement="top" title="<%= gettext("Copy Address") %>" aria-label="Copy Address"> <button type="button" class="button button-secondary button-sm" id="button" data-toggle="tooltip" data-placement="top">
Copy Contract Source Code Copy Contract Source Code
</button> </button>
</span> </span>
@ -90,7 +90,7 @@
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3>Contract ABI</h3> <h3>Contract ABI</h3>
<span class="icon-links" data-clipboard-text="<%= format_smart_contract_abi(@address.smart_contract.abi) %>"> <span class="icon-links" data-clipboard-text="<%= format_smart_contract_abi(@address.smart_contract.abi) %>">
<button type="button" class="button button--secondary button--sm" id="button"> <button type="button" class="button button-secondary button-sm" id="button">
Copy Contract ABI Copy Contract ABI
</button> </button>
</span> </span>
@ -110,7 +110,7 @@
<div class="d-flex justify-content-between align-items-baseline"> <div class="d-flex justify-content-between align-items-baseline">
<h3>Contract creation code</h3> <h3>Contract creation code</h3>
<span class="icon-links" data-clipboard-text="<%= @address.contract_code %>"> <span class="icon-links" data-clipboard-text="<%= @address.contract_code %>">
<button type="button" class="button button--secondary button--sm" id="button"> <button type="button" class="button button-secondary button-sm" id="button">
Copy Contract Creation Code Copy Contract Creation Code
</button> </button>
</span> </span>

@ -53,15 +53,15 @@
name="button" name="button"
id="loading" id="loading"
disabled="true" disabled="true"
class="d-none px-4 position-absolute button button--primary button--sm mr-2"> class="d-none px-4 position-absolute button button-primary button-sm mr-2">
<i class="fa fa-spinner fa-spin"></i> <%= gettext("loading.....") %> <i class="fa fa-spinner fa-spin"></i> <%= gettext("loading.....") %>
</button> </button>
<%= submit "Verify and publish", class: "button button--primary button--sm mr-2", "data-loading": "animation" %> <%= submit "Verify and publish", class: "button button-primary button-sm mr-2", "data-loading": "animation" %>
<%= reset "Reset", class: "button button--secondary button--sm mr-2" %> <%= reset "Reset", class: "button button-secondary button-sm mr-2" %>
<%= link( <%= link(
"Cancel", "Cancel",
to: address_contract_path(@conn, :index, @conn.params["address_id"]), to: address_contract_path(@conn, :index, @conn.params["address_id"]),
class: "button button--sm") %> class: "button button-sm") %>
<% end %> <% end %>
</div> </div>
</div> </div>

@ -78,8 +78,18 @@
</ul> </ul>
</div> </div>
<div class="card-body"> <div class="card-body">
<div data-selector="channel-batching-message" style="display:none;">
<div data-selector="reload-button" class="alert alert-info">
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More internal transactions have come in" %></a>
</div>
</div>
<div data-selector="channel-disconnected-message" style="display:none;">
<div data-selector="reload-button" class="alert alert-danger">
<a href="#" class="alert-link"><%= gettext "Connection Lost, click to load newer internal transactions" %></a>
</div>
</div>
<div class="dropdown float-right u-push-sm"> <div class="dropdown float-right u-push-sm">
<button data-test="filter_dropdown" class="button button--secondary button--xsmall dropdown-toggle" type="button" <button data-test="filter_dropdown" class="button button-secondary button-xs dropdown-toggle" type="button"
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filter: <%= format_current_filter(@filter) %> Filter: <%= format_current_filter(@filter) %>
</button> </button>
@ -116,19 +126,21 @@
</div> </div>
<h2 class="card-title"><%= gettext "Internal Transactions" %></h2> <h2 class="card-title"><%= gettext "Internal Transactions" %></h2>
<%= if Enum.count(@internal_transactions) > 0 do %> <%= if Enum.count(@internal_transactions) > 0 do %>
<%= for internal_transaction <- @internal_transactions do %> <span data-selector="internal-transactions-list">
<%= render "_internal_transaction.html", address: @address, internal_transaction: internal_transaction %> <%= for internal_transaction <- @internal_transactions do %>
<% end %> <%= render "_internal_transaction.html", address: @address, internal_transaction: internal_transaction %>
<% end %>
</span>
<% else %> <% else %>
<div class="tile tile-muted text-center"> <div class="tile tile-muted text-center">
<span><%= gettext "There are no internal transactions for this address." %></span> <span data-selector="empty-internal-transactions-list"><%= gettext "There are no internal transactions for this address." %></span>
</div> </div>
<% end %> <% end %>
<div> <div>
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: address_internal_transaction_path( to: address_internal_transaction_path(
@conn, @conn,
:index, :index,

@ -90,7 +90,7 @@
</div> </div>
</div> </div>
<div class="dropdown float-right u-push-sm"> <div class="dropdown float-right u-push-sm">
<button data-test="filter_dropdown" class="button button--secondary button--xsmall dropdown-toggle" type="button" <button data-test="filter_dropdown" class="button button-secondary button-xs dropdown-toggle" type="button"
id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Filter: <%= format_current_filter(@filter) %> Filter: <%= format_current_filter(@filter) %>
</button> </button>
@ -141,7 +141,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: address_transaction_path( to: address_transaction_path(
@conn, @conn,
:index, :index,

@ -18,8 +18,8 @@
<div class="collapse multi-collapse" id="<%= action_tile_id(@module_name, @action.name) %>"> <div class="collapse multi-collapse" id="<%= action_tile_id(@module_name, @action.name) %>">
<div class="card card-body pt-3 rounded"> <div class="card card-body pt-3 rounded">
<h4 class="text-primary"><%= gettext "Parameters" %> <h4 class="text-primary"><%= gettext "Parameters" %>
<button data-selector="<%= "#{@module_name}-#{@action.name}-btn-try-api" %>" role="button" class="button button--small button--primary float-right" style="width: 8rem" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Try it out" %></button> <button data-selector="<%= "#{@module_name}-#{@action.name}-btn-try-api" %>" role="button" class="button button-sm button-primary float-right" style="width: 8rem" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Try it out" %></button>
<button data-selector="<%= "#{@module_name}-#{@action.name}-btn-try-api-cancel" %>" role="button" class="collapse button button--small button--secondary float-right" style="width: 8rem" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Cancel" %></button> <button data-selector="<%= "#{@module_name}-#{@action.name}-btn-try-api-cancel" %>" role="button" class="collapse button button-sm button-secondary float-right" style="width: 8rem" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Cancel" %></button>
</h4> </h4>
<div class="mb-4"> <div class="mb-4">
@ -89,10 +89,10 @@
<% end %> <% end %>
<div class="row"> <div class="row">
<div class="col-sm-2 offset-sm-4 col-md-2 offset-md-2"> <div class="col-sm-2 offset-sm-4 col-md-2 offset-md-2">
<button data-selector="<%= "#{@module_name}-#{@action.name}-try-api-ui" %>" data-try-api-ui-button-type="execute" role="button" class="collapse button button--primary w-100" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Execute" %></button> <button data-selector="<%= "#{@module_name}-#{@action.name}-try-api-ui" %>" data-try-api-ui-button-type="execute" role="button" class="collapse button button-primary w-100" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Execute" %></button>
</div> </div>
<div class="col-sm-2 col-md-2"> <div class="col-sm-2 col-md-2">
<button role="button" class="collapse button button--secondary" data-selector="<%= "#{@module_name}-#{@action.name}-btn-try-api-clear" %>" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Clear" %></button> <button role="button" class="collapse button button-secondary" data-selector="<%= "#{@module_name}-#{@action.name}-btn-try-api-clear" %>" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Clear" %></button>
</div> </div>
</div> </div>
<!-- /btn-group --> <!-- /btn-group -->

@ -13,7 +13,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: block_path( to: block_path(
@conn, @conn,
:index, :index,

@ -48,7 +48,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: transaction_path( to: transaction_path(
@conn, @conn,
:index, :index,

@ -52,7 +52,7 @@
<section class="container"> <section class="container">
<div class="card card-chain-blocks"> <div class="card card-chain-blocks">
<div class="card-body"> <div class="card-body">
<%= link(gettext("View All Blocks →"), to: block_path(BlockScoutWeb.Endpoint, :index), class: "button button--secondary button--xsmall float-right") %> <%= link(gettext("View All Blocks →"), to: block_path(BlockScoutWeb.Endpoint, :index), class: "button button-secondary button-xsmall float-right") %>
<h2 class="card-title"><%= gettext "Blocks" %></h2> <h2 class="card-title"><%= gettext "Blocks" %></h2>
<div class="row" data-selector="chain-block-list"> <div class="row" data-selector="chain-block-list">
<%= for block <- @blocks do %> <%= for block <- @blocks do %>
@ -69,7 +69,7 @@
<a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a> <a href="#" class="alert-link"><span data-selector="channel-batching-count"></span> <%= gettext "More transactions have come in" %></a>
</div> </div>
</div> </div>
<%= link(gettext("View All Transactions →"), to: transaction_path(BlockScoutWeb.Endpoint, :index), class: "button button--secondary button--xsmall float-right") %> <%= link(gettext("View All Transactions →"), to: transaction_path(BlockScoutWeb.Endpoint, :index), class: "button button-secondary button-xsmall float-right") %>
<h2 class="card-title"><%= gettext "Transactions" %></h2> <h2 class="card-title"><%= gettext "Transactions" %></h2>
<span data-selector="transactions-list"> <span data-selector="transactions-list">
<%= for transaction <- @transactions do %> <%= for transaction <- @transactions do %>

@ -76,7 +76,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: pending_transaction_path( to: pending_transaction_path(
@conn, @conn,
:index, :index,

@ -19,7 +19,7 @@
</div> </div>
<% end %> <% end %>
<input type="submit" value='<%= gettext("Query")%>' class="button button--secondary button--xsmall py-0 mt-2" /> <input type="submit" value='<%= gettext("Query")%>' class="button button-secondary button-xs py-0 mt-2" />
</form> </form>
<div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'> <div class='p-2 text-muted <%= if (queryable?(function["inputs"]) == true), do: "w-100" %>'>

@ -3,6 +3,18 @@
<div class="col-md-12 col-lg-8"> <div class="col-md-12 col-lg-8">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<div class="icon-links float-right">
<span data-clipboard-text="<%= @token.contract_address_hash %>">
<button type="button" class="icon-link" id="button" data-toggle="tooltip" data-placement="top" title="<%= gettext("Copy Address") %>" aria-label="Copy Address">
<i class="fas fa-clone"></i>
</button>
</span>
<span data-toggle="modal" data-target="#qrModal">
<button type="button" class="icon-link" data-toggle="tooltip" data-placement="top" title="<%= gettext("QR Code") %>" aria-label="Show QR Code">
<i class="fas fa-qrcode"></i>
</button>
</span>
</div>
<h1 class="card-title"> <h1 class="card-title">
<%= if token_name?(@token) do %> <%= if token_name?(@token) do %>
<%= @token.name %> <%= @token.name %>
@ -11,9 +23,12 @@
<% end %> <% end %>
</h1> </h1>
<h3><%= to_string(@token.contract_address_hash) %></h3> <h3><%= to_string(@token.contract_address_hash) %></h3>
<div class="d-flex flex-row justify-content-start text-muted"> <div class="d-flex flex-row justify-content-start text-muted">
<span class="mr-4"> <%= @token.type %> </span>
<span class="mr-4"><%= @total_address_in_token_transfers %> <%= gettext "addresses" %></span> <span class="mr-4"><%= @total_address_in_token_transfers %> <%= gettext "addresses" %></span>
<span class="mr-4"><%= @total_token_transfers %> <%= gettext "Transfers" %></span> <span class="mr-4"><%= @total_token_transfers %> <%= gettext "Transfers" %></span>
<%= if decimals?(@token) do %> <%= if decimals?(@token) do %>
@ -50,3 +65,22 @@
</div> </div>
<% end %> <% end %>
</section> </section>
<!-- Modal -->
<div class="modal fade" id="qrModal" tabindex="-1" role="dialog" aria-labelledby="qrModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="qrModalLabel"><%= gettext "QR Code" %></h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<img src="data:image/png;base64, <%= BlockScoutWeb.AddressView.qr_code(@token.contract_address) %> " class="qr-code" alt="qr_code" title="<%= @token.contract_address %>" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal"><%= gettext "Close" %></button>
</div>
</div>
</div>
</div>

@ -69,7 +69,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--small float-right mt-4", class: "button button-secondary button-small float-right mt-4",
to: token_path(@conn, :show, @token.contract_address_hash, @next_page_params) to: token_path(@conn, :show, @token.contract_address_hash, @next_page_params)
) %> ) %>
<% end %> <% end %>

@ -64,7 +64,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: transaction_path( to: transaction_path(
@conn, @conn,
:index, :index,

@ -75,7 +75,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Newer"), gettext("Newer"),
class: "button button--secondary button--sm float-right mt-3", class: "button button-secondary button-sm float-right mt-3",
to: transaction_internal_transaction_path( to: transaction_internal_transaction_path(
@conn, @conn,
:index, :index,

@ -120,7 +120,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Newer"), gettext("Newer"),
class: "button button--secondary button--sm u-float-right mt-3", class: "button button-secondary button-sm u-float-right mt-3",
to: transaction_log_path( to: transaction_log_path(
@conn, @conn,
:index, :index,

@ -70,7 +70,7 @@
<%= if @next_page_params do %> <%= if @next_page_params do %>
<%= link( <%= link(
gettext("Older"), gettext("Older"),
class: "button button--secondary button--sm u-float-left mt-3", class: "button button-secondary button-sm u-float-left mt-3",
to: transaction_token_transfer_path( to: transaction_token_transfer_path(
@conn, @conn,
:index, :index,

@ -1,7 +1,3 @@
defmodule BlockScoutWeb.BlockTransactionView do defmodule BlockScoutWeb.BlockTransactionView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias BlockScoutWeb.BlockView
defdelegate formatted_timestamp(block), to: BlockView
end end

@ -134,7 +134,7 @@ msgstr ""
msgid "Address" msgid "Address"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:105 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:115
#: lib/block_scout_web/templates/address_transaction/index.html.eex:116 #: lib/block_scout_web/templates/address_transaction/index.html.eex:116
#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10
@ -150,7 +150,7 @@ msgstr ""
msgid "Success" msgid "Success"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:94 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:104
#: lib/block_scout_web/templates/address_transaction/index.html.eex:105 #: lib/block_scout_web/templates/address_transaction/index.html.eex:105
#: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9
#: lib/block_scout_web/views/address_transaction_view.ex:9 #: lib/block_scout_web/views/address_transaction_view.ex:9
@ -305,7 +305,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:117 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:127
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:60 #: lib/block_scout_web/templates/address_transaction/index.html.eex:60
@ -354,7 +354,7 @@ msgstr ""
msgid "Wei" msgid "Wei"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:98
#: lib/block_scout_web/templates/address_transaction/index.html.eex:99 #: lib/block_scout_web/templates/address_transaction/index.html.eex:99
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 #: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11
@ -476,7 +476,7 @@ msgstr ""
msgid "There are no Transactions" msgid "There are no Transactions"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:130 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:142
#: lib/block_scout_web/templates/address_transaction/index.html.eex:143 #: lib/block_scout_web/templates/address_transaction/index.html.eex:143
#: lib/block_scout_web/templates/block/index.html.eex:15 #: lib/block_scout_web/templates/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50 #: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -588,7 +588,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:8 #: lib/block_scout_web/templates/address/overview.html.eex:8
#: lib/block_scout_web/templates/address_contract/index.html.eex:75 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:8
msgid "Copy Address" msgid "Copy Address"
msgstr "" msgstr ""
@ -602,11 +602,13 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13 #: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:64 #: lib/block_scout_web/templates/address/overview.html.eex:64
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:73
msgid "QR Code" msgid "QR Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:124 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:136
msgid "There are no internal transactions for this address." msgid "There are no internal transactions for this address."
msgstr "" msgstr ""
@ -696,6 +698,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:73 #: lib/block_scout_web/templates/address/overview.html.eex:73
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82
msgid "Close" msgid "Close"
msgstr "" msgstr ""
@ -738,7 +741,7 @@ msgid "Token Transfer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:18 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:33
msgid "Transfers" msgid "Transfers"
msgstr "" msgstr ""
@ -771,7 +774,7 @@ msgid "There are no transfers for this Token."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:22
msgid "Token Details" msgid "Token Details"
msgstr "" msgstr ""
@ -795,17 +798,17 @@ msgid "Token Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:17 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:32
msgid "addresses" msgid "addresses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:20 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35
msgid "decimals" msgid "decimals"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:31 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:46
msgid "Total Supply" msgid "Total Supply"
msgstr "" msgstr ""
@ -1010,3 +1013,13 @@ msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:42 #: lib/block_scout_web/views/transaction_view.ex:42
msgid "Max of" msgid "Max of"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88
msgid "Connection Lost, click to load newer internal transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
msgid "More internal transactions have come in"
msgstr ""

@ -146,7 +146,7 @@ msgstr "%{count} transactions in this block"
msgid "Address" msgid "Address"
msgstr "Address" msgstr "Address"
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:105 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:115
#: lib/block_scout_web/templates/address_transaction/index.html.eex:116 #: lib/block_scout_web/templates/address_transaction/index.html.eex:116
#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10
@ -162,7 +162,7 @@ msgstr "Overview"
msgid "Success" msgid "Success"
msgstr "Success" msgstr "Success"
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:94 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:104
#: lib/block_scout_web/templates/address_transaction/index.html.eex:105 #: lib/block_scout_web/templates/address_transaction/index.html.eex:105
#: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9
#: lib/block_scout_web/views/address_transaction_view.ex:9 #: lib/block_scout_web/views/address_transaction_view.ex:9
@ -317,7 +317,7 @@ msgstr ""
#: lib/block_scout_web/templates/address_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:117 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:127
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17
#: lib/block_scout_web/templates/address_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:60 #: lib/block_scout_web/templates/address_transaction/index.html.eex:60
@ -366,7 +366,7 @@ msgstr ""
msgid "Wei" msgid "Wei"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:98
#: lib/block_scout_web/templates/address_transaction/index.html.eex:99 #: lib/block_scout_web/templates/address_transaction/index.html.eex:99
#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 #: lib/block_scout_web/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11
@ -488,7 +488,7 @@ msgstr ""
msgid "There are no Transactions" msgid "There are no Transactions"
msgstr "" msgstr ""
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:130 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:142
#: lib/block_scout_web/templates/address_transaction/index.html.eex:143 #: lib/block_scout_web/templates/address_transaction/index.html.eex:143
#: lib/block_scout_web/templates/block/index.html.eex:15 #: lib/block_scout_web/templates/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50 #: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -600,7 +600,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:8 #: lib/block_scout_web/templates/address/overview.html.eex:8
#: lib/block_scout_web/templates/address_contract/index.html.eex:75 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:8
msgid "Copy Address" msgid "Copy Address"
msgstr "" msgstr ""
@ -614,11 +614,13 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:13 #: lib/block_scout_web/templates/address/overview.html.eex:13
#: lib/block_scout_web/templates/address/overview.html.eex:64 #: lib/block_scout_web/templates/address/overview.html.eex:64
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:73
msgid "QR Code" msgid "QR Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:124 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:136
msgid "There are no internal transactions for this address." msgid "There are no internal transactions for this address."
msgstr "" msgstr ""
@ -708,6 +710,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:73 #: lib/block_scout_web/templates/address/overview.html.eex:73
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82
msgid "Close" msgid "Close"
msgstr "" msgstr ""
@ -750,7 +753,7 @@ msgid "Token Transfer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:18 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:33
msgid "Transfers" msgid "Transfers"
msgstr "" msgstr ""
@ -783,7 +786,7 @@ msgid "There are no transfers for this Token."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:22
msgid "Token Details" msgid "Token Details"
msgstr "" msgstr ""
@ -807,17 +810,17 @@ msgid "Token Transfers"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:17 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:32
msgid "addresses" msgid "addresses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:20 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35
msgid "decimals" msgid "decimals"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:31 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:46
msgid "Total Supply" msgid "Total Supply"
msgstr "" msgstr ""
@ -1022,3 +1025,13 @@ msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:42 #: lib/block_scout_web/views/transaction_view.ex:42
msgid "Max of" msgid "Max of"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88
msgid "Connection Lost, click to load newer internal transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83
msgid "More internal transactions have come in"
msgstr ""

@ -4,6 +4,24 @@ defmodule BlockScoutWeb.ChainTest do
alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Chain.{Address, Block, Transaction}
alias BlockScoutWeb.Chain alias BlockScoutWeb.Chain
describe "current_filter/1" do
test "sets direction based on to filter" do
assert [direction: :to] = Chain.current_filter(%{"filter" => "to"})
end
test "sets direction based on from filter" do
assert [direction: :from] = Chain.current_filter(%{"filter" => "from"})
end
test "no direction set" do
assert [] = Chain.current_filter(%{})
end
test "no direction set with paging_options" do
assert [paging_options: "test"] = Chain.current_filter(%{paging_options: "test"})
end
end
describe "from_param/1" do describe "from_param/1" do
test "finds a block by block number with a valid block number" do test "finds a block by block number with a valid block number" do
%Block{number: number} = insert(:block, number: 37) %Block{number: number} = insert(:block, number: 37)

@ -110,5 +110,75 @@ defmodule BlockScoutWeb.AddressChannelTest do
100 -> assert true 100 -> assert true
end end
end end
test "notified of new_internal_transaction for matching from_address", %{address: address, topic: topic} do
transaction =
:transaction
|> insert(from_address: address)
|> with_block()
internal_transaction = insert(:internal_transaction, transaction: transaction, from_address: address, index: 0)
Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]})
receive do
%Phoenix.Socket.Broadcast{topic: ^topic, event: "internal_transaction", payload: payload} ->
assert payload.address.hash == address.hash
assert payload.internal_transaction.id == internal_transaction.id
after
5_000 ->
assert false, "Expected message received nothing."
end
end
test "notified of new_internal_transaction for matching to_address", %{address: address, topic: topic} do
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
internal_transaction = insert(:internal_transaction, transaction: transaction, to_address: address, index: 0)
Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]})
receive do
%Phoenix.Socket.Broadcast{topic: ^topic, event: "internal_transaction", payload: payload} ->
assert payload.address.hash == address.hash
assert payload.internal_transaction.id == internal_transaction.id
after
5_000 ->
assert false, "Expected message received nothing."
end
end
test "not notified twice of new_internal_transaction if to and from address are equal", %{
address: address,
topic: topic
} do
transaction =
:transaction
|> insert(from_address: address, to_address: address)
|> with_block()
internal_transaction =
insert(:internal_transaction, transaction: transaction, from_address: address, to_address: address, index: 0)
Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]})
receive do
%Phoenix.Socket.Broadcast{topic: ^topic, event: "internal_transaction", payload: payload} ->
assert payload.address.hash == address.hash
assert payload.internal_transaction.id == internal_transaction.id
after
5_000 ->
assert false, "Expected message received nothing."
end
receive do
_ -> assert false, "Received duplicate broadcast."
after
100 -> assert true
end
end
end end
end end

@ -7,6 +7,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
alias Explorer.ExchangeRates alias Explorer.ExchangeRates
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.ExchangeRates.Source.TestSource alias Explorer.ExchangeRates.Source.TestSource
alias Explorer.Market
setup :verify_on_exit! setup :verify_on_exit!
@ -25,7 +26,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
market_cap_usd: Decimal.new("1000000.0"), market_cap_usd: Decimal.new("1000000.0"),
name: "test", name: "test",
symbol: Explorer.coin(), symbol: Explorer.coin(),
usd_value: Decimal.new("1.0"), usd_value: Decimal.new("2.5"),
volume_24h_usd: Decimal.new("1000.0") volume_24h_usd: Decimal.new("1000.0")
} }
@ -36,20 +37,55 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do
{:ok, %{token: token}} {:ok, %{token: token}}
end end
test "subscribed user is notified of new_rate event", %{token: token} do describe "new_rate" do
ExchangeRates.handle_info({nil, {:ok, [token]}}, %{}) test "subscribed user is notified", %{token: token} do
ExchangeRates.handle_info({nil, {:ok, [token]}}, %{})
topic = "exchange_rate:new_rate" topic = "exchange_rate:new_rate"
@endpoint.subscribe(topic) @endpoint.subscribe(topic)
Notifier.handle_event({:chain_event, :exchange_rate}) Notifier.handle_event({:chain_event, :exchange_rate})
receive do receive do
%Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} -> %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} ->
assert payload.exchange_rate == token assert payload.exchange_rate == token
after assert payload.market_history_data == []
5_000 -> after
assert false, "Expected message received nothing." 5_000 ->
assert false, "Expected message received nothing."
end
end
test "subscribed user is notified with market history", %{token: token} do
ExchangeRates.handle_info({nil, {:ok, [token]}}, %{})
today = Date.utc_today()
old_records =
for i <- 1..29 do
%{
date: Timex.shift(today, days: i * -1),
closing_price: Decimal.new(1)
}
end
records = [%{date: today, closing_price: token.usd_value} | old_records]
Market.bulk_insert_history(records)
topic = "exchange_rate:new_rate"
@endpoint.subscribe(topic)
Notifier.handle_event({:chain_event, :exchange_rate})
receive do
%Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} ->
assert payload.exchange_rate == token
assert payload.market_history_data == records
after
5_000 ->
assert false, "Expected message received nothing."
end
end end
end end
end end

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.AddressControllerTest do
conn = get(conn, "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") conn = get(conn, "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
assert redirected_to(conn) =~ "/en/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed/transactions" assert redirected_to(conn) =~ "/address/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed/transactions"
end end
end end
end end

@ -8,7 +8,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
test "without AJAX", %{conn: conn} do test "without AJAX", %{conn: conn} do
%Address{hash: hash} = Factory.insert(:address) %Address{hash: hash} = Factory.insert(:address)
response_conn = get(conn, address_token_balance_path(conn, :index, :en, to_string(hash))) response_conn = get(conn, address_token_balance_path(conn, :index, to_string(hash)))
assert html_response(response_conn, 404) assert html_response(response_conn, 404)
end end
@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
test "with AJAX without valid address", %{conn: conn} do test "with AJAX without valid address", %{conn: conn} do
ajax_conn = ajax(conn) ajax_conn = ajax(conn)
response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, :en, "invalid_address")) response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, "invalid_address"))
assert html_response(response_conn, 404) assert html_response(response_conn, 404)
end end
@ -24,7 +24,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
test "with AJAX with valid address without address still returns token balances", %{conn: conn} do test "with AJAX with valid address without address still returns token balances", %{conn: conn} do
ajax_conn = ajax(conn) ajax_conn = ajax(conn)
response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, :en, Factory.address_hash())) response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, Factory.address_hash()))
assert html_response(response_conn, 200) assert html_response(response_conn, 200)
end end
@ -34,7 +34,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
ajax_conn = ajax(conn) ajax_conn = ajax(conn)
response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, :en, hash)) response_conn = get(ajax_conn, address_token_balance_path(ajax_conn, :index, hash))
assert html_response(response_conn, 200) assert html_response(response_conn, 200)
end end

@ -985,6 +985,63 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
test "with filterby=to option", %{conn: conn} do
block = insert(:block)
address = insert(:address)
insert(:transaction, from_address: address)
|> with_block(block)
insert(:transaction, to_address: address)
|> with_block(block)
params = %{
"module" => "account",
"action" => "txlist",
"address" => "#{address.hash}",
"filterby" => "to"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 1
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "with filterby=from option", %{conn: conn} do
block = insert(:block)
address = insert(:address)
insert(:transaction, from_address: address)
|> with_block(block)
insert(:transaction, from_address: address)
|> with_block(block)
insert(:transaction, to_address: address)
|> with_block(block)
params = %{
"module" => "account",
"action" => "txlist",
"address" => "#{address.hash}",
"filterby" => "from"
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
assert length(response["result"]) == 2
assert response["status"] == "1"
assert response["message"] == "OK"
end
test "supports GET and POST requests", %{conn: conn} do test "supports GET and POST requests", %{conn: conn} do
address = insert(:address) address = insert(:address)
@ -1706,16 +1763,22 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert AddressController.optional_params(params3) == %{} assert AddressController.optional_params(params3) == %{}
end end
test "'filterby' value can only be 'to'" do test "'filterby' value can be 'to' or 'from'" do
params1 = %{"filterby" => "to"} params1 = %{"filterby" => "to"}
optional_params = AddressController.optional_params(params1) optional_params1 = AddressController.optional_params(params1)
assert optional_params.filter_by == "to" assert optional_params1.filter_by == "to"
params2 = %{"filterby" => "from"}
params2 = %{"filterby" => "invalid"} optional_params2 = AddressController.optional_params(params2)
assert AddressController.optional_params(params2) == %{} assert optional_params2.filter_by == "from"
params3 = %{"filterby" => "invalid"}
assert AddressController.optional_params(params3) == %{}
end end
test "only includes optional params when they're given" do test "only includes optional params when they're given" do

@ -6,6 +6,14 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
setup :verify_on_exit! setup :verify_on_exit!
describe "GET index/3" do describe "GET index/3" do
test "error for invalid address", %{conn: conn} do
path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: "0x00")
conn = get(conn, path)
assert conn.status == 404
end
test "only responds to ajax requests", %{conn: conn} do test "only responds to ajax requests", %{conn: conn} do
smart_contract = insert(:smart_contract) smart_contract = insert(:smart_contract)
@ -36,6 +44,21 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end end
describe "GET show/3" do describe "GET show/3" do
test "error for invalid address", %{conn: conn} do
path =
smart_contract_path(
BlockScoutWeb.Endpoint,
:show,
"0x00",
function_name: "get",
args: []
)
conn = get(conn, path)
assert conn.status == 404
end
test "only responds to ajax requests", %{conn: conn} do test "only responds to ajax requests", %{conn: conn} do
smart_contract = insert(:smart_contract) smart_contract = insert(:smart_contract)

@ -18,6 +18,6 @@ defmodule BlockScoutWeb.AddressContractPage do
end end
defp address_contract_path(address) do defp address_contract_path(address) do
"/en/address/#{address.hash}/contracts" "/address/#{address.hash}/contracts"
end end
end end

@ -31,15 +31,19 @@ defmodule BlockScoutWeb.AddressPage do
css("[data-test='address_detail_hash']", text: to_string(address_hash)) css("[data-test='address_detail_hash']", text: to_string(address_hash))
end end
def internal_transaction(%InternalTransaction{id: id}) do
css("[data-test='internal_transaction'][data-internal-transaction-id='#{id}']")
end
def internal_transactions(count: count) do def internal_transactions(count: count) do
css("[data-test='internal_transaction']", count: count) css("[data-test='internal_transaction']", count: count)
end end
def internal_transaction_address_link(%InternalTransaction{id: id, to_address_hash: address_hash}, :to) do def internal_transaction_address_link(%InternalTransaction{id: id, from_address_hash: address_hash}, :from) do
css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end end
def internal_transaction_address_link(%InternalTransaction{id: id, from_address_hash: address_hash}, :from) do def internal_transaction_address_link(%InternalTransaction{id: id, to_address_hash: address_hash}, :to) do
css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end end
@ -55,11 +59,11 @@ defmodule BlockScoutWeb.AddressPage do
css("[data-transaction-hash='#{transaction_hash}']") css("[data-transaction-hash='#{transaction_hash}']")
end end
def transaction_address_link(%Transaction{hash: hash, to_address_hash: address_hash}, :to) do def transaction_address_link(%Transaction{hash: hash, from_address_hash: address_hash}, :from) do
css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end end
def transaction_address_link(%Transaction{hash: hash, from_address_hash: address_hash}, :from) do def transaction_address_link(%Transaction{hash: hash, to_address_hash: address_hash}, :to) do
css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']")
end end
@ -91,8 +95,4 @@ defmodule BlockScoutWeb.AddressPage do
def token_transfers_expansion(%Transaction{hash: transaction_hash}) do def token_transfers_expansion(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']") css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']")
end end
def transaction_type do
css("[data-test='transaction_type']")
end
end end

@ -15,10 +15,6 @@ defmodule BlockScoutWeb.ChainPage do
css("[data-test='contract-creation'] [data-address-hash='#{hash}']") css("[data-test='contract-creation'] [data-address-hash='#{hash}']")
end end
def non_loaded_transaction_count(count) do
css("[data-selector='channel-batching-count']", text: count)
end
def search(session, text) do def search(session, text) do
session session
|> fill_in(css("[data-test='search_input']"), with: text) |> fill_in(css("[data-test='search_input']"), with: text)
@ -37,14 +33,6 @@ defmodule BlockScoutWeb.ChainPage do
css("[data-test='chain_transaction']", count: count) css("[data-test='chain_transaction']", count: count)
end end
def transaction(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}']")
end
def transaction_status(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='transaction_status']")
end
def visit_page(session) do def visit_page(session) do
visit(session, "/") visit(session, "/")
end end

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.ContractVerifyPage do
import Wallaby.Query import Wallaby.Query
def visit_page(session, address_hash) do def visit_page(session, address_hash) do
visit(session, "/en/address/#{address_hash}/contract_verifications/new") visit(session, "/address/#{address_hash}/contract_verifications/new")
end end
def fill_form(session, %{ def fill_form(session, %{

@ -19,10 +19,6 @@ defmodule BlockScoutWeb.TransactionListPage do
css("[data-transaction-hash='#{hash}'] [data-test='transaction_type']", text: "Contract Creation") css("[data-transaction-hash='#{hash}'] [data-test='transaction_type']", text: "Contract Creation")
end end
def non_loaded_transaction_count(count) do
css("[data-selector='channel-batching-count']", text: count)
end
def transaction(%Transaction{hash: transaction_hash}) do def transaction(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}']") css("[data-transaction-hash='#{transaction_hash}']")
end end

@ -18,8 +18,4 @@ defmodule BlockScoutWeb.TransactionPage do
def visit_page(session, %Transaction{hash: transaction_hash}) do def visit_page(session, %Transaction{hash: transaction_hash}) do
visit(session, "/tx/#{transaction_hash}") visit(session, "/tx/#{transaction_hash}")
end end
def visit_page(session, transaction_hash = %Hash{}) do
visit(session, "/tx/#{transaction_hash}")
end
end end

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
alias Explorer.Chain.Wei alias Explorer.Chain.Wei
alias Explorer.Factory alias Explorer.Factory
alias BlockScoutWeb.{AddressPage, AddressView} alias BlockScoutWeb.{AddressPage, AddressView, Notifier}
setup do setup do
block = insert(:block) block = insert(:block)
@ -169,6 +169,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
session session
|> AddressPage.visit_page(addresses.lincoln) |> AddressPage.visit_page(addresses.lincoln)
|> assert_has(AddressPage.transaction_address_link(transactions.from_lincoln, :to)) |> assert_has(AddressPage.transaction_address_link(transactions.from_lincoln, :to))
|> refute_has(AddressPage.transaction_address_link(transactions.from_lincoln, :from))
end end
end end
@ -216,6 +217,28 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> AddressPage.visit_page(addresses.lincoln) |> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions() |> AddressPage.click_internal_transactions()
|> assert_has(AddressPage.internal_transaction_address_link(internal_transaction, :from)) |> assert_has(AddressPage.internal_transaction_address_link(internal_transaction, :from))
|> refute_has(AddressPage.internal_transaction_address_link(internal_transaction, :to))
end
test "viewing new internal transactions via live update", %{addresses: addresses, session: session} do
transaction =
:transaction
|> insert(from_address: addresses.lincoln)
|> with_block()
session
|> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions()
|> assert_has(AddressPage.internal_transactions(count: 2))
internal_transaction =
insert(:internal_transaction, transaction: transaction, index: 0, from_address: addresses.lincoln)
Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]})
session
|> assert_has(AddressPage.internal_transactions(count: 3))
|> assert_has(AddressPage.internal_transaction(internal_transaction))
end end
end end

@ -0,0 +1,29 @@
defmodule BlockScoutWeb.BlockViewTest do
use BlockScoutWeb.ConnCase, async: true
alias BlockScoutWeb.BlockView
alias Explorer.Repo
describe "average_gas_price/1" do
test "returns an average of the gas prices for a block's transactions with the unit value" do
block = insert(:block)
Enum.each(1..10, fn index ->
:transaction
|> insert(gas_price: 10_000_000_000 * index)
|> with_block(block)
end)
assert "55 Gwei" == BlockView.average_gas_price(Repo.preload(block, [:transactions]))
end
end
describe "formatted_timestamp/1" do
test "returns a formatted timestamp string for a block" do
block = insert(:block)
assert Timex.format!(block.timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) ==
BlockView.formatted_timestamp(block)
end
end
end

@ -17,7 +17,7 @@ defmodule BlockScoutWeb.TransactionViewTest do
gas_used: nil gas_used: nil
) )
expected_value = "max of 0.009 POA" expected_value = "Max of 0.009 POA"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether) assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
end end

@ -6,25 +6,6 @@ defmodule EthereumJSONRPC.ReceiptTest do
doctest Receipt doctest Receipt
describe "to_elixir/1" do describe "to_elixir/1" do
test "with status with nil raises ArgumentError with full receipt" do
assert_raise ArgumentError,
"""
Could not convert receipt to elixir
Receipt:
%{"status" => nil, "transactionHash" => "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"}
Errors:
{:unknown_value, %{key: "status", value: nil}}
""",
fn ->
Receipt.to_elixir(%{
"status" => nil,
"transactionHash" => "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"
})
end
end
test "with new key raise ArgumentError with full receipt" do test "with new key raise ArgumentError with full receipt" do
assert_raise ArgumentError, assert_raise ArgumentError,
""" """

@ -48,7 +48,8 @@ defmodule Explorer.Chain do
@typedoc """ @typedoc """
Event type where data is broadcasted whenever data is inserted from chain indexing. Event type where data is broadcasted whenever data is inserted from chain indexing.
""" """
@type chain_event :: :addresses | :balances | :blocks | :exchange_rate | :logs | :transactions @type chain_event ::
:addresses | :balances | :blocks | :exchange_rate | :internal_transactions | :logs | :transactions
@type direction :: :from | :to @type direction :: :from | :to
@ -1212,7 +1213,7 @@ defmodule Explorer.Chain do
""" """
@spec subscribe_to_events(chain_event()) :: :ok @spec subscribe_to_events(chain_event()) :: :ok
def subscribe_to_events(event_type) def subscribe_to_events(event_type)
when event_type in ~w(addresses balances blocks exchange_rate logs transactions)a do when event_type in ~w(addresses balances blocks exchange_rate internal_transactions logs transactions)a do
Registry.register(Registry.ChainEvents, event_type, []) Registry.register(Registry.ChainEvents, event_type, [])
:ok :ok
end end

@ -209,7 +209,8 @@ defmodule Explorer.Chain.Import do
end end
defp broadcast_events(data) do defp broadcast_events(data) do
for {event_type, event_data} <- data, event_type in ~w(addresses balances blocks logs transactions)a do for {event_type, event_data} <- data,
event_type in ~w(addresses balances blocks internal_transactions logs transactions)a do
broadcast_event_data(event_type, event_data) broadcast_event_data(event_type, event_data)
end end
end end
@ -666,7 +667,7 @@ defmodule Explorer.Chain.Import do
conflict_target: [:transaction_hash, :index], conflict_target: [:transaction_hash, :index],
for: InternalTransaction, for: InternalTransaction,
on_conflict: :replace_all, on_conflict: :replace_all,
returning: [:index, :transaction_hash], returning: [:id, :index, :transaction_hash],
timeout: timeout, timeout: timeout,
timestamps: timestamps timestamps: timestamps
) )
@ -674,7 +675,7 @@ defmodule Explorer.Chain.Import do
{:ok, {:ok,
for( for(
internal_transaction <- internal_transactions, internal_transaction <- internal_transactions,
do: Map.take(internal_transaction, [:index, :transaction_hash]) do: Map.take(internal_transaction, [:id, :index, :transaction_hash])
)} )}
end end

@ -226,6 +226,10 @@ defmodule Explorer.Etherscan do
where(query, [t], t.to_address_hash == ^address_hash) where(query, [t], t.to_address_hash == ^address_hash)
end end
defp where_address_match(query, address_hash, %{filter_by: "from"}) do
where(query, [t], t.from_address_hash == ^address_hash)
end
defp where_address_match(query, address_hash, _) do defp where_address_match(query, address_hash, _) do
query query
|> where([t], t.to_address_hash == ^address_hash) |> where([t], t.to_address_hash == ^address_hash)

@ -417,6 +417,12 @@ defmodule Explorer.Chain.ImportTest do
assert_received {:chain_event, :blocks, [%Block{}]} assert_received {:chain_event, :blocks, [%Block{}]}
end end
test "publishes internal_transaction data to subscribers on insert" do
Chain.subscribe_to_events(:internal_transactions)
Import.all(@import_data)
assert_received {:chain_event, :internal_transactions, [%{id: _}, %{id: _}]}
end
test "publishes log data to subscribers on insert" do test "publishes log data to subscribers on insert" do
Chain.subscribe_to_events(:logs) Chain.subscribe_to_events(:logs)
Import.all(@import_data) Import.all(@import_data)

@ -379,6 +379,39 @@ defmodule Explorer.EtherscanTest do
assert length(found_transactions) == 0 assert length(found_transactions) == 0
end end
test "with filter_by: 'from' option with one matching transaction" do
address = insert(:address)
:transaction
|> insert(to_address: address)
|> with_block()
:transaction
|> insert(from_address: address)
|> with_block()
options = %{filter_by: "from"}
found_transactions = Etherscan.list_transactions(address.hash, options)
assert length(found_transactions) == 1
end
test "with filter_by: 'from' option with non-matching transaction" do
address = insert(:address)
other_address = insert(:address)
:transaction
|> insert(from_address: other_address, to_address: nil)
|> with_block()
options = %{filter_by: "from"}
found_transactions = Etherscan.list_transactions(address.hash, options)
assert length(found_transactions) == 0
end
end end
describe "list_internal_transactions/1" do describe "list_internal_transactions/1" do

@ -262,6 +262,17 @@ defmodule Explorer.Factory do
} }
end end
def internal_transaction_suicide_factory() do
%InternalTransaction{
from_address: build(:address),
trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction
type: :suicide,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}
end
def log_factory do def log_factory do
%Log{ %Log{
address: build(:address), address: build(:address),

@ -4,7 +4,7 @@ defmodule Indexer.TokenFetcher do
""" """
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Token alias Explorer.Chain.{Hash, Token}
alias Explorer.Chain.Hash.Address alias Explorer.Chain.Hash.Address
alias Explorer.SmartContract.Reader alias Explorer.SmartContract.Reader
alias Indexer.BufferedTask alias Indexer.BufferedTask
@ -153,10 +153,42 @@ defmodule Indexer.TokenFetcher do
|> Map.from_struct() |> Map.from_struct()
|> Map.put(:cataloged, true) |> Map.put(:cataloged, true)
|> Map.merge(token_contract_data) |> Map.merge(token_contract_data)
|> handle_invalid_strings()
end end
defp atomized_key("decimals"), do: :decimals defp atomized_key("decimals"), do: :decimals
defp atomized_key("name"), do: :name defp atomized_key("name"), do: :name
defp atomized_key("symbol"), do: :symbol defp atomized_key("symbol"), do: :symbol
defp atomized_key("totalSupply"), do: :total_supply defp atomized_key("totalSupply"), do: :total_supply
# It's a temp fix to store tokens that have names and/or symbols with characters that the database
# doesn't accept. See https://github.com/poanetwork/blockscout/issues/669 for more info.
defp handle_invalid_strings(%{name: name, symbol: symbol, contract_address_hash: contract_address_hash} = token) do
name = handle_invalid_name(name, contract_address_hash)
symbol = handle_invalid_symbol(symbol)
%{token | name: name, symbol: symbol}
end
defp handle_invalid_name(nil, _contract_address_hash), do: nil
defp handle_invalid_name(name, contract_address_hash) do
case String.valid?(name) do
true -> name
false -> format_according_contract_address_hash(contract_address_hash)
end
end
defp handle_invalid_symbol(symbol) do
case String.valid?(symbol) do
true -> symbol
false -> nil
end
end
defp format_according_contract_address_hash(contract_address_hash) do
contract_address_hash
|> Hash.to_string()
|> String.slice(0, 6)
end
end end

@ -52,13 +52,13 @@ defmodule Indexer.BlockFetcher.RealtimeTest do
EthereumJSONRPC.Mox EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [ |> expect(:json_rpc, fn [
%{ %{
id: 3_946_079, id: 0,
jsonrpc: "2.0", jsonrpc: "2.0",
method: "eth_getBlockByNumber", method: "eth_getBlockByNumber",
params: ["0x3C365F", true] params: ["0x3C365F", true]
}, },
%{ %{
id: 3_946_080, id: 1,
jsonrpc: "2.0", jsonrpc: "2.0",
method: "eth_getBlockByNumber", method: "eth_getBlockByNumber",
params: ["0x3C3660", true] params: ["0x3C3660", true]
@ -68,7 +68,7 @@ defmodule Indexer.BlockFetcher.RealtimeTest do
{:ok, {:ok,
[ [
%{ %{
id: 3_946_079, id: 0,
jsonrpc: "2.0", jsonrpc: "2.0",
result: %{ result: %{
"author" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2", "author" => "0x5ee341ac44d344ade1ca3a771c59b98eb2a77df2",
@ -127,7 +127,7 @@ defmodule Indexer.BlockFetcher.RealtimeTest do
} }
}, },
%{ %{
id: 3_946_080, id: 1,
jsonrpc: "2.0", jsonrpc: "2.0",
result: %{ result: %{
"author" => "0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3", "author" => "0x66c9343c7e8ca673a1fedf9dbf2cd7936dbbf7e3",

@ -4,7 +4,7 @@ defmodule Indexer.TokenFetcherTest do
import Mox import Mox
alias Explorer.Chain alias Explorer.{Chain, Repo}
alias Explorer.Chain.Token alias Explorer.Chain.Token
alias Indexer.TokenFetcher alias Indexer.TokenFetcher
@ -74,5 +74,119 @@ defmodule Indexer.TokenFetcherTest do
}} = Chain.token_from_address_hash(contract_address_hash) }} = Chain.token_from_address_hash(contract_address_hash)
end end
end end
test "considers the contract address formatted hash when it is an invalid string", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "decimals",
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
},
%{
id: "name",
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001aa796568616e7a652067676761202075797575206e6e6e6e6e200000000000000"
},
%{
id: "symbol",
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003424e540000000000000000000000000000000000000000000000000000000000"
},
%{
id: "totalSupply",
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
]}
end
)
assert TokenFetcher.run([contract_address_hash], 0, json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, name: "0x0000"}} = Chain.token_from_address_hash(contract_address_hash)
end
end
test "considers the symbol nil when it is an invalid string", %{json_rpc_named_arguments: json_rpc_named_arguments} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "decimals",
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
},
%{
id: "name",
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003424e540000000000000000000000000000000000000000000000000000000000"
},
%{
id: "symbol",
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001aa796568616e7a652067676761202075797575206e6e6e6e6e200000000000000"
},
%{
id: "totalSupply",
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
]}
end
)
assert TokenFetcher.run([contract_address_hash], 0, json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, symbol: nil}} = Chain.token_from_address_hash(contract_address_hash)
end
end
test "considers name as nil when the name is nil", %{json_rpc_named_arguments: json_rpc_named_arguments} do
token = insert(:token, name: nil, symbol: nil, total_supply: nil, decimals: nil, cataloged: false)
contract_address_hash = token.contract_address_hash
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
1,
fn [%{id: "decimals"}, %{id: "name"}, %{id: "symbol"}, %{id: "totalSupply"}], _opts ->
{:ok,
[
%{
id: "decimals",
result: "0x0000000000000000000000000000000000000000000000000000000000000012"
},
%{
id: "symbol",
result:
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001aa796568616e7a652067676761202075797575206e6e6e6e6e200000000000000"
},
%{
id: "totalSupply",
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
]}
end
)
assert TokenFetcher.run([contract_address_hash], 0, json_rpc_named_arguments) == :ok
assert {:ok, %Token{cataloged: true, name: nil}} = Chain.token_from_address_hash(contract_address_hash)
end
end
end end
end end

Loading…
Cancel
Save