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
else
mix coveralls.circle --exclude no_geth --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so single done here
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done"
# if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
(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
- store_artifacts:
@ -388,8 +388,8 @@ jobs:
mix coveralls.html --exclude no_geth --parallel --umbrella
else
mix coveralls.circle --exclude no_geth --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so signal done here
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done"
# if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
(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
- store_artifacts:
@ -438,8 +438,8 @@ jobs:
mix coveralls.html --exclude no_parity --parallel --umbrella
else
mix coveralls.circle --exclude no_parity --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so signal done here
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done"
# if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
(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
- store_artifacts:
@ -488,8 +488,8 @@ jobs:
mix coveralls.html --exclude no_parity --parallel --umbrella
else
mix coveralls.circle --exclude no_parity --parallel --umbrella ||
# if mix failed, then coveralls_merge won't run, so single done here
curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d "payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done"
# if mix failed, then coveralls_merge won't run, so signal done here and return original exit status
(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
- 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;
transition: all 0.25s;
&--primary {
&-primary {
background-color: $primary;
color: $white;
border: 1px solid $primary;
&:hover,
&:focus {
background-color: $primary;
border-color: $primary;
background-color: darken($primary, 10%);
border-color: darken($primary, 10%);
text-decoration: none;
}
@ -29,50 +29,51 @@
}
}
&--secondary {
&-secondary {
border: 1px solid $tertiary;
color: $tertiary;
font-weight: 400;
&:hover,
&:focus {
background-color: darken($tertiary, 20%);
border-color: darken($tertiary, 20%);
background-color: darken($tertiary, 10%);
border-color: darken($tertiary, 10%);
color: $white;
text-decoration: none;
outline: none !important;
}
}
&--xsmall {
&-xs {
font-size: 11px;
padding: 6px 9px 6px !important;
}
&--small {
&-sm {
font-size: 12px;
padding: 10px 20px 10px;
}
&--medium {
&-md {
font-size: 1.5rem;
padding: 15px 30px 15px;
}
&--large {
&-lg {
font-size: 1.5rem;
padding: 20px 60px 20px;
}
// Block button
// --------------------------------------------------
// -------------------------
&--block {
&-block {
display: block;
width: 100%;
}
&--disabled {
&-disabled,
&:disabled, {
background-color: $gray-300;
color: $gray-500;
text-decoration: none;
@ -81,6 +82,6 @@
}
// Vertically space out multiple block buttons
.button--block + .button--block {
.button-block + .button-block {
margin-top: 5px;
}

@ -31,7 +31,7 @@ $grays: map-merge((
$blue: #4786ff !default;
$indigo: #5b33a1 !default;
$purple: #9987fc !default;
$purple: #997fdc !default;
$pink: #e83e8c !default;
$red: #c74d39 !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 { batchChannel, initRedux } from '../utils'
import { updateAllAges } from '../lib/from_now'
import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown'
const BATCH_THRESHOLD = 10
@ -14,6 +15,7 @@ export const initialState = {
beyondPageOne: null,
channelDisconnected: false,
filter: null,
newInternalTransactions: [],
newTransactions: [],
balance: null,
transactionCount: null
@ -42,6 +44,29 @@ export function reducer (state = initialState, action) {
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': {
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, {
main (store) {
const { addressHash, blockNumber } = params
const channel = socket.channel(`addresses:${addressHash}`, {})
store.dispatch({
const { addressHash } = params
const addressChannel = socket.channel(`addresses:${addressHash}`, {})
const state = store.dispatch({
type: 'PAGE_LOAD',
params,
transactionCount: $('[data-selector="transaction-count"]').text()
})
channel.join()
channel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
channel.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 })))
addressChannel.join()
addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' }))
addressChannel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg }))
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) {
const $balance = $('[data-selector="balance-card"]')
const $channelBatching = $('[data-selector="channel-batching-message"]')
const $channelBatchingCount = $('[data-selector="channel-batching-count"]')
const $channelDisconnected = $('[data-selector="channel-disconnected-message"]')
const $emptyInternalTransactionsList = $('[data-selector="empty-internal-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 $transactionsList = $('[data-selector="transactions-list"]')
if ($emptyInternalTransactionsList.length && state.newInternalTransactions.length) window.location.reload()
if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload()
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 (state.batchCountAccumulator) {
$channelBatching.show()
@ -105,6 +144,9 @@ router.when('/address/:addressHash').then((params) => initRedux(reducer, {
} else {
$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) {
$transactionsList.prepend(state.newTransactions.slice(oldState.newTransactions.length).reverse().join(''))
updateAllAges()

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

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

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

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
use BlockScoutWeb, :controller
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.ExchangeRates.Token
@ -46,24 +46,4 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
not_found(conn)
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

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
use BlockScoutWeb, :controller
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.ExchangeRates.Token
@ -46,24 +46,4 @@ defmodule BlockScoutWeb.AddressTransactionController do
not_found(conn)
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

@ -322,7 +322,7 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
defp put_filter_by(options, 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)
_ ->

@ -661,7 +661,7 @@ defmodule BlockScoutWeb.Etherscan do
description: """
A string representing the field to filter by. If none is given
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(:blocks)
Chain.subscribe_to_events(:exchange_rate)
Chain.subscribe_to_events(:internal_transactions)
Chain.subscribe_to_events(:transactions)
{:ok, []}
end

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Notifier do
"""
alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.Address
alias Explorer.Chain.{Address, InternalTransaction}
alias Explorer.ExchangeRates.Token
alias BlockScoutWeb.Endpoint
@ -36,6 +36,12 @@ defmodule BlockScoutWeb.Notifier do
})
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
transaction_hashes
|> Chain.hashes_to_transactions(
@ -65,6 +71,24 @@ defmodule BlockScoutWeb.Notifier do
})
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
Endpoint.broadcast("transactions:new_transaction", "new_transaction", %{
transaction: transaction
@ -75,7 +99,7 @@ defmodule BlockScoutWeb.Notifier do
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", %{
address: transaction.to_address,
transaction: transaction

@ -9,7 +9,7 @@
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>">
</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;">
<i class="fa fa-spinner fa-spin"></i>
<%= gettext("Fetching tokens...") %>

@ -47,7 +47,7 @@
<%= link(
gettext("Verify and Publish"),
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"
) %>
<% end %>
@ -72,7 +72,7 @@
<div class="d-flex justify-content-between align-items-baseline">
<h3>Contract source code</h3>
<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
</button>
</span>
@ -90,7 +90,7 @@
<div class="d-flex justify-content-between align-items-baseline">
<h3>Contract ABI</h3>
<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
</button>
</span>
@ -110,7 +110,7 @@
<div class="d-flex justify-content-between align-items-baseline">
<h3>Contract creation code</h3>
<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
</button>
</span>

@ -53,15 +53,15 @@
name="button"
id="loading"
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.....") %>
</button>
<%= 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" %>
<%= 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" %>
<%= link(
"Cancel",
to: address_contract_path(@conn, :index, @conn.params["address_id"]),
class: "button button--sm") %>
class: "button button-sm") %>
<% end %>
</div>
</div>

@ -78,8 +78,18 @@
</ul>
</div>
<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">
<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">
Filter: <%= format_current_filter(@filter) %>
</button>
@ -116,19 +126,21 @@
</div>
<h2 class="card-title"><%= gettext "Internal Transactions" %></h2>
<%= if Enum.count(@internal_transactions) > 0 do %>
<%= for internal_transaction <- @internal_transactions do %>
<%= render "_internal_transaction.html", address: @address, internal_transaction: internal_transaction %>
<% end %>
<span data-selector="internal-transactions-list">
<%= for internal_transaction <- @internal_transactions do %>
<%= render "_internal_transaction.html", address: @address, internal_transaction: internal_transaction %>
<% end %>
</span>
<% else %>
<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>
<% end %>
<div>
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -90,7 +90,7 @@
</div>
</div>
<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">
Filter: <%= format_current_filter(@filter) %>
</button>
@ -141,7 +141,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -18,8 +18,8 @@
<div class="collapse multi-collapse" id="<%= action_tile_id(@module_name, @action.name) %>">
<div class="card card-body pt-3 rounded">
<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-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" %>" 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-sm button-secondary float-right" style="width: 8rem" data-module="<%= @module_name %>" data-action="<%= @action.name %>"><%= gettext "Cancel" %></button>
</h4>
<div class="mb-4">
@ -89,10 +89,10 @@
<% end %>
<div class="row">
<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 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>
<!-- /btn-group -->

@ -13,7 +13,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -48,7 +48,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -52,7 +52,7 @@
<section class="container">
<div class="card card-chain-blocks">
<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>
<div class="row" data-selector="chain-block-list">
<%= 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>
</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>
<span data-selector="transactions-list">
<%= for transaction <- @transactions do %>

@ -76,7 +76,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -19,7 +19,7 @@
</div>
<% 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>
<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="card">
<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">
<%= if token_name?(@token) do %>
<%= @token.name %>
@ -11,9 +23,12 @@
<% end %>
</h1>
<h3><%= to_string(@token.contract_address_hash) %></h3>
<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_token_transfers %> <%= gettext "Transfers" %></span>
<%= if decimals?(@token) do %>
@ -50,3 +65,22 @@
</div>
<% end %>
</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 %>
<%= link(
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)
) %>
<% end %>

@ -64,7 +64,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -75,7 +75,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -120,7 +120,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

@ -70,7 +70,7 @@
<%= if @next_page_params do %>
<%= link(
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(
@conn,
:index,

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

@ -134,7 +134,7 @@ msgstr ""
msgid "Address"
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/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:10
@ -150,7 +150,7 @@ msgstr ""
msgid "Success"
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/views/address_internal_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_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: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_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:60
@ -354,7 +354,7 @@ msgstr ""
msgid "Wei"
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/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11
@ -476,7 +476,7 @@ msgstr ""
msgid "There are no Transactions"
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/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -588,7 +588,7 @@ msgstr ""
#, elixir-format
#: 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"
msgstr ""
@ -602,11 +602,13 @@ msgstr ""
#, elixir-format
#: 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/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:73
msgid "QR Code"
msgstr ""
#, 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."
msgstr ""
@ -696,6 +698,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:73
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82
msgid "Close"
msgstr ""
@ -738,7 +741,7 @@ msgid "Token Transfer"
msgstr ""
#, 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"
msgstr ""
@ -771,7 +774,7 @@ msgid "There are no transfers for this Token."
msgstr ""
#, 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"
msgstr ""
@ -795,17 +798,17 @@ msgid "Token Transfers"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
@ -1010,3 +1013,13 @@ msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:42
msgid "Max of"
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"
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/views/address_internal_transaction_view.ex:10
#: lib/block_scout_web/views/address_transaction_view.ex:10
@ -162,7 +162,7 @@ msgstr "Overview"
msgid "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/views/address_internal_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_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: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_transaction/index.html.eex:20
#: lib/block_scout_web/templates/address_transaction/index.html.eex:60
@ -366,7 +366,7 @@ msgstr ""
msgid "Wei"
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/views/address_internal_transaction_view.ex:11
#: lib/block_scout_web/views/address_transaction_view.ex:11
@ -488,7 +488,7 @@ msgstr ""
msgid "There are no Transactions"
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/block/index.html.eex:15
#: lib/block_scout_web/templates/block_transaction/index.html.eex:50
@ -600,7 +600,7 @@ msgstr ""
#, elixir-format
#: 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"
msgstr ""
@ -614,11 +614,13 @@ msgstr ""
#, elixir-format
#: 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/tokens/overview/_details.html.eex:13
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:73
msgid "QR Code"
msgstr ""
#, 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."
msgstr ""
@ -708,6 +710,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:73
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82
msgid "Close"
msgstr ""
@ -750,7 +753,7 @@ msgid "Token Transfer"
msgstr ""
#, 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"
msgstr ""
@ -783,7 +786,7 @@ msgid "There are no transfers for this Token."
msgstr ""
#, 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"
msgstr ""
@ -807,17 +810,17 @@ msgid "Token Transfers"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
#, 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"
msgstr ""
@ -1022,3 +1025,13 @@ msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:42
msgid "Max of"
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 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
test "finds a block by block number with a valid block number" do
%Block{number: number} = insert(:block, number: 37)

@ -110,5 +110,75 @@ defmodule BlockScoutWeb.AddressChannelTest do
100 -> assert true
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

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

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

@ -8,7 +8,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
test "without AJAX", %{conn: conn} do
%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)
end
@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
test "with AJAX without valid address", %{conn: conn} do
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)
end
@ -24,7 +24,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
test "with AJAX with valid address without address still returns token balances", %{conn: conn} do
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)
end
@ -34,7 +34,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceControllerTest do
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)
end

@ -985,6 +985,63 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert response["message"] == "OK"
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
address = insert(:address)
@ -1706,16 +1763,22 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
assert AddressController.optional_params(params3) == %{}
end
test "'filterby' value can only be 'to'" do
test "'filterby' value can be 'to' or 'from'" do
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
test "only includes optional params when they're given" do

@ -6,6 +6,14 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
setup :verify_on_exit!
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
smart_contract = insert(:smart_contract)
@ -36,6 +44,21 @@ defmodule BlockScoutWeb.SmartContractControllerTest do
end
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
smart_contract = insert(:smart_contract)

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

@ -31,15 +31,19 @@ defmodule BlockScoutWeb.AddressPage do
css("[data-test='address_detail_hash']", text: to_string(address_hash))
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
css("[data-test='internal_transaction']", count: count)
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}']")
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}']")
end
@ -55,11 +59,11 @@ defmodule BlockScoutWeb.AddressPage do
css("[data-transaction-hash='#{transaction_hash}']")
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}']")
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}']")
end
@ -91,8 +95,4 @@ defmodule BlockScoutWeb.AddressPage do
def token_transfers_expansion(%Transaction{hash: transaction_hash}) do
css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']")
end
def transaction_type do
css("[data-test='transaction_type']")
end
end

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

@ -6,7 +6,7 @@ defmodule BlockScoutWeb.ContractVerifyPage do
import Wallaby.Query
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
def fill_form(session, %{

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

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

@ -3,7 +3,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
alias Explorer.Chain.Wei
alias Explorer.Factory
alias BlockScoutWeb.{AddressPage, AddressView}
alias BlockScoutWeb.{AddressPage, AddressView, Notifier}
setup do
block = insert(:block)
@ -169,6 +169,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
session
|> AddressPage.visit_page(addresses.lincoln)
|> assert_has(AddressPage.transaction_address_link(transactions.from_lincoln, :to))
|> refute_has(AddressPage.transaction_address_link(transactions.from_lincoln, :from))
end
end
@ -216,6 +217,28 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
|> AddressPage.visit_page(addresses.lincoln)
|> AddressPage.click_internal_transactions()
|> 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

@ -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
)
expected_value = "max of 0.009 POA"
expected_value = "Max of 0.009 POA"
assert expected_value == TransactionView.formatted_fee(transaction, denomination: :ether)
end

@ -6,25 +6,6 @@ defmodule EthereumJSONRPC.ReceiptTest do
doctest Receipt
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
assert_raise ArgumentError,
"""

@ -48,7 +48,8 @@ defmodule Explorer.Chain do
@typedoc """
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
@ -1212,7 +1213,7 @@ defmodule Explorer.Chain do
"""
@spec subscribe_to_events(chain_event()) :: :ok
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, [])
:ok
end

@ -209,7 +209,8 @@ defmodule Explorer.Chain.Import do
end
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)
end
end
@ -666,7 +667,7 @@ defmodule Explorer.Chain.Import do
conflict_target: [:transaction_hash, :index],
for: InternalTransaction,
on_conflict: :replace_all,
returning: [:index, :transaction_hash],
returning: [:id, :index, :transaction_hash],
timeout: timeout,
timestamps: timestamps
)
@ -674,7 +675,7 @@ defmodule Explorer.Chain.Import do
{:ok,
for(
internal_transaction <- internal_transactions,
do: Map.take(internal_transaction, [:index, :transaction_hash])
do: Map.take(internal_transaction, [:id, :index, :transaction_hash])
)}
end

@ -226,6 +226,10 @@ defmodule Explorer.Etherscan do
where(query, [t], t.to_address_hash == ^address_hash)
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
query
|> where([t], t.to_address_hash == ^address_hash)

@ -417,6 +417,12 @@ defmodule Explorer.Chain.ImportTest do
assert_received {:chain_event, :blocks, [%Block{}]}
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
Chain.subscribe_to_events(:logs)
Import.all(@import_data)

@ -379,6 +379,39 @@ defmodule Explorer.EtherscanTest do
assert length(found_transactions) == 0
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
describe "list_internal_transactions/1" do

@ -262,6 +262,17 @@ defmodule Explorer.Factory do
}
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
%Log{
address: build(:address),

@ -4,7 +4,7 @@ defmodule Indexer.TokenFetcher do
"""
alias Explorer.Chain
alias Explorer.Chain.Token
alias Explorer.Chain.{Hash, Token}
alias Explorer.Chain.Hash.Address
alias Explorer.SmartContract.Reader
alias Indexer.BufferedTask
@ -153,10 +153,42 @@ defmodule Indexer.TokenFetcher do
|> Map.from_struct()
|> Map.put(:cataloged, true)
|> Map.merge(token_contract_data)
|> handle_invalid_strings()
end
defp atomized_key("decimals"), do: :decimals
defp atomized_key("name"), do: :name
defp atomized_key("symbol"), do: :symbol
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

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

@ -4,7 +4,7 @@ defmodule Indexer.TokenFetcherTest do
import Mox
alias Explorer.Chain
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Token
alias Indexer.TokenFetcher
@ -74,5 +74,119 @@ defmodule Indexer.TokenFetcherTest do
}} = Chain.token_from_address_hash(contract_address_hash)
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

Loading…
Cancel
Save