Merge remote-tracking branch 'origin/np-fix-constructor-args-bug' into production-xdai-stg

pull/8050/head
Viktor Baranov 3 years ago
commit 86220e9dae
  1. 26
      .github/workflows/config.yml
  2. 9
      CHANGELOG.md
  3. 2
      apps/block_scout_web/assets/__tests__/lib/currency.js
  4. 180
      apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js
  5. 1
      apps/block_scout_web/assets/css/app.scss
  6. 5
      apps/block_scout_web/assets/css/components/_address-overview.scss
  7. 2
      apps/block_scout_web/assets/css/components/_btn_no_border.scss
  8. 10
      apps/block_scout_web/assets/css/components/_btn_wallet.scss
  9. 8
      apps/block_scout_web/assets/css/components/_dropdown.scss
  10. 2
      apps/block_scout_web/assets/css/components/_transaction.scss
  11. 2
      apps/block_scout_web/assets/css/theme/_dark-theme.scss
  12. 2
      apps/block_scout_web/assets/js/lib/ad.js
  13. 16
      apps/block_scout_web/assets/js/lib/currency.js
  14. 7
      apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js
  15. 11
      apps/block_scout_web/assets/js/lib/token_balance_dropdown.js
  16. 51
      apps/block_scout_web/assets/js/pages/address.js
  17. 16
      apps/block_scout_web/assets/package-lock.json
  18. 4
      apps/block_scout_web/assets/webpack.config.js
  19. 8
      apps/block_scout_web/lib/block_scout_web/chain.ex
  20. 2
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  21. 19
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  22. 10
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex
  23. 12
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
  24. 16
      apps/block_scout_web/lib/block_scout_web/templates/address/_balance_dropdown.html.eex
  25. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex
  26. 22
      apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex
  27. 41
      apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
  28. 278
      apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
  29. 8
      apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
  30. 8
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  31. 87
      apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex
  32. 59
      apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
  33. 64
      apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex
  34. 14
      apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex
  35. 133
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
  36. 6
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_tokens.html.eex
  37. 7
      apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
  38. 12
      apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
  39. 2
      apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex
  40. 4
      apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
  41. 4
      apps/block_scout_web/lib/block_scout_web/templates/bridged_tokens/index.html.eex
  42. 2
      apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
  43. 7
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_csv_export_button.html.eex
  44. 167
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex
  45. 2
      apps/block_scout_web/lib/block_scout_web/templates/search/results.html.eex
  46. 4
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_top.html.eex
  47. 4
      apps/block_scout_web/lib/block_scout_web/templates/tokens/index.html.eex
  48. 9
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input.html.eex
  49. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
  50. 26
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
  51. 9
      apps/block_scout_web/lib/block_scout_web/views/address_token_balance_view.ex
  52. 1
      apps/block_scout_web/lib/block_scout_web/views/address_token_transfer_view.ex
  53. 4
      apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex
  54. 45
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  55. 20
      apps/block_scout_web/lib/block_scout_web/web_router.ex
  56. 3
      apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs
  57. 2
      apps/block_scout_web/test/block_scout_web/controllers/address_token_controller_test.exs
  58. 16
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  59. 3
      apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs
  60. 32
      apps/block_scout_web/test/block_scout_web/views/address_token_balance_view_test.exs
  61. 75
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  62. 4
      apps/explorer/config/config.exs
  63. 1
      apps/explorer/lib/explorer/application.ex
  64. 125
      apps/explorer/lib/explorer/chain.ex
  65. 32
      apps/explorer/lib/explorer/chain/address/current_token_balance.ex
  66. 41
      apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
  67. 2
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  68. 20
      apps/explorer/lib/explorer/chain/log.ex
  69. 14
      apps/explorer/lib/explorer/chain/smart_contract.ex
  70. 22
      apps/explorer/lib/explorer/chain/transaction.ex
  71. 119
      apps/explorer/lib/explorer/counters/address_token_transfers_counter.ex
  72. 4
      apps/explorer/lib/explorer/market/market.ex
  73. 6
      apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex
  74. 42
      apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
  75. 2
      apps/explorer/lib/explorer/smart_contract/vyper_downloader.ex
  76. 4
      apps/explorer/test/explorer/chain/address/current_token_balance_test.exs
  77. 8
      apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs
  78. 12
      apps/explorer/test/explorer/chain/address_log_csv_explorer_test.exs
  79. 4
      apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs
  80. 8
      apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs
  81. 2
      apps/explorer/test/explorer/chain_test.exs
  82. 11
      apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex

@ -38,7 +38,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-
@ -98,7 +98,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -122,7 +122,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -145,7 +145,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -154,7 +154,7 @@ jobs:
id: dialyzer-cache
with:
path: priv/plts
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-"
@ -185,7 +185,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -211,7 +211,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -239,7 +239,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -288,7 +288,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -350,7 +350,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -406,7 +406,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -473,7 +473,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"
@ -534,7 +534,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_2-${{ hashFiles('mix.lock') }}
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_5-${{ hashFiles('mix.lock') }}
restore-keys: |
${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-"

@ -1,6 +1,10 @@
## Current
### Features
- [#4739](https://github.com/blockscout/blockscout/pull/4739) - Improve logs and inputs decoding
- [#4747](https://github.com/blockscout/blockscout/pull/4747) - Advanced CSV export
- [#4745](https://github.com/blockscout/blockscout/pull/4745) - Vyper contracts verification
- [#4699](https://github.com/blockscout/blockscout/pull/4699) - Address page facelifting
- [#4667](https://github.com/blockscout/blockscout/pull/4667) - Transaction Page: Add expand/collapse button for long contract method data
- [#4641](https://github.com/blockscout/blockscout/pull/4641), [#4733](https://github.com/blockscout/blockscout/pull/4733) - Improve Read Contract page logic
- [#4660](https://github.com/blockscout/blockscout/pull/4660) - Save Sourcify path instead of filename
@ -16,6 +20,10 @@
- [#4579](https://github.com/blockscout/blockscout/pull/4579) - Write contract page: Resize inputs; Improve multiplier selector
### Fixes
- [#4764](https://github.com/blockscout/blockscout/pull/4764) - Add cleaning of substrings of `require` messages from parsed constructor arguments
- [#4751](https://github.com/blockscout/blockscout/pull/4751) - Change text and link for `trade STAKE` button
- [#4746](https://github.com/blockscout/blockscout/pull/4746) - Fix comparison of decimal value
- [#4711](https://github.com/blockscout/blockscout/pull/4711) - Add trimming to the contract functions inputs
- [#4729](https://github.com/blockscout/blockscout/pull/4729) - Fix bugs with fees in cases of txs with `gas price = 0`
- [#4725](https://github.com/blockscout/blockscout/pull/4725) - Fix hardcoded coin name on transaction's and block's page
- [#4717](https://github.com/blockscout/blockscout/pull/4717) - Contract verification fix: check only success creation tx
@ -39,6 +47,7 @@
- [#4546](https://github.com/blockscout/blockscout/pull/4546) - Indexer performance update: async get block rewards
### Chore
- [#4735](https://github.com/blockscout/blockscout/pull/4735) - Code clean up: Remove clauses for outdated ganache bugs
- [#4726](https://github.com/blockscout/blockscout/pull/4726) - Update chart.js
- [#4707](https://github.com/blockscout/blockscout/pull/4707) - Top navigation: Move Accounts tab to Tokens
- [#4704](https://github.com/blockscout/blockscout/pull/4704) - Update to Erlang/OTP 24

@ -4,7 +4,7 @@ test('formatUsdValue', () => {
window.localized = {
'Less than': 'Less than'
}
expect(formatUsdValue(0)).toEqual('$0.000000 USD')
expect(formatUsdValue(0)).toEqual('$0.00 USD')
expect(formatUsdValue(0.0000001)).toEqual('Less than $0.000001 USD')
expect(formatUsdValue(0.123456789)).toEqual('$0.123457 USD')
expect(formatUsdValue(0.1234)).toEqual('$0.123400 USD')

@ -0,0 +1,180 @@
import { prepareMethodArgs } from '../../../js/lib/smart_contract/common_helpers'
import $ from 'jquery'
const oneFieldHTML =
'<form data-function-form>' +
' <input type="hidden" name="function_name" value="convertMultiple">' +
' <input type="hidden" name="method_id" value="">' +
' <div>' +
' <input type="text" name="function_input" id="first">' +
' </div>' +
' <input type="submit" value="Write">' +
'</form>'
const twoFieldHTML =
'<form data-function-form>' +
' <input type="hidden" name="function_name" value="convertMultiple">' +
' <input type="hidden" name="method_id" value="">' +
' <div>' +
' <input type="text" name="function_input" id="first">' +
' </div>' +
' <div>' +
' <input type="text" name="function_input" id="second">' +
' </div>' +
' <input type="submit" value="Write">' +
'</form>'
test('prepare contract args | type: address[]*2', () => {
document.body.innerHTML = twoFieldHTML
var inputs = [
{
"type": "address[]",
"name": "arg1",
"internalType": "address[]"
},
{
"type": "address[]",
"name": "arg2",
"internalType": "address[]"
}
]
document.getElementById('first').value = ' 0x0000000000000000000000000000000000000000 , 0x0000000000000000000000000000000000000001 '
document.getElementById('second').value = ' 0x0000000000000000000000000000000000000002 , 0x0000000000000000000000000000000000000003 '
const expectedValue = [
[
'0x0000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000001'
],
[
'0x0000000000000000000000000000000000000002',
'0x0000000000000000000000000000000000000003'
]
]
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: address', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "address",
"name": "arg1",
"internalType": "address"
}
]
document.getElementById('first').value = ' 0x000000000000000000 0000000000000000000000 '
const expectedValue = ['0x0000000000000000000000000000000000000000']
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: string', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "string",
"name": "arg1",
"internalType": "string"
}
]
document.getElementById('first').value = ' 0x0000000000000000000000000000000000000000 , 0x0000000000000000000000000000000000000001 '
const expectedValue = ['0x0000000000000000000000000000000000000000 , 0x0000000000000000000000000000000000000001']
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: string[]', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "string[]",
"name": "arg1",
"internalType": "string[]"
}
]
document.getElementById('first').value = ' " 0x0000000000000000000000000000000000000000 " , " 0x0000000000000000000000000000000000000001 " '
const expectedValue = [['0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000001']]
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: bool[]', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "bool[]",
"name": "arg1",
"internalType": "bool[]"
}
]
document.getElementById('first').value = ' true , false '
const expectedValue = [[true, false]]
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: bool', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "bool",
"name": "arg1",
"internalType": "bool"
}
]
document.getElementById('first').value = ' fals e '
const expectedValue = [false]
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: uint256', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "uint256",
"name": "arg1",
"internalType": "uint256"
}
]
document.getElementById('first').value = ' 9 876 543 210 '
const expectedValue = ['9876543210']
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})
test('prepare contract args | type: uint256[]', () => {
document.body.innerHTML = oneFieldHTML
var inputs = [
{
"type": "uint256[]",
"name": "arg1",
"internalType": "uint256[]"
}
]
document.getElementById('first').value = ' 156 000 , 10 690 000 , 59874 '
const expectedValue = [['156000', '10690000', '59874']]
const $functionInputs = $('[data-function-form]').find('input[name=function_input]')
expect(prepareMethodArgs($functionInputs, inputs)).toEqual(expectedValue)
})

@ -101,6 +101,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/btn_add_to_mm";
@import "components/btn_copy";
@import "components/btn_qr";
@import "components/btn_wallet";
@import "components/btn_contract";
@import "components/btn_address_card";
@import "components/btn_dropdown_line";

@ -1,9 +1,8 @@
.address-detail-hash-title {
color: #333;
font-size: 12px;
font-size: 14px;
font-weight: bold;
line-height: 1.2;
margin: 0 0 12px;
text-align: left;
}
@ -66,8 +65,6 @@
}
.address-current-balance {
font-size: 12px;
font-weight: 200;
line-height: 1.2;
margin: 0 0 12px;
font-weight: 400;

@ -11,7 +11,7 @@ $btn-no-border-color: $primary !default;
}
}
.btn-no-border-transactions {
.btn-no-border-link-to-tems {
background-color: rgba($primary, 0.1) !important;
border-color: #00000000 !important;
align-items: center;

@ -0,0 +1,10 @@
$btn-wallet-color: $btn-line-color !default;
$btn-wallet-dimensions: 31px !default;
.btn-wallet-icon {
@include square-icon-button($btn-wallet-color, $btn-wallet-dimensions);
color: $btn-wallet-color;
&:hover {
color: #fff;
}
}

@ -118,6 +118,14 @@ $dropdown-menu-item-hover-background: rgba($secondary, 0.1) !default;
.dropdown-toggle::after {
margin-left: 0.71em;
font-size: 12px !important;
}
.token-balance-dropdown {
transform: none !important;
top: 20px !important;
left: unset !important;
width: 300px !important;
}
.dropdown-toggle.swap::after {

@ -60,7 +60,7 @@
}
}
.transaction-details {
.fs-14 {
font-size: 14px;
}
.transaction-gas-used {

@ -1174,7 +1174,7 @@ $dark-stakes-banned-background: #3e314c;
color: $labels-dark;
}
.dark-theme-applied .btn-no-border-transactions {
.dark-theme-applied .btn-no-border-link-to-tems {
border-color: $dark-primary !important;
color: $dark-primary !important;
}

@ -12,10 +12,12 @@ function showAd () {
if (domainName.endsWith('blockscout.com')) {
$('.js-ad-dependant-mb-2').addClass('mb-2')
$('.js-ad-dependant-mb-3').addClass('mb-3')
$('.js-ad-dependant-mb-5-reverse').removeClass('mb-5')
return true
} else {
$('.js-ad-dependant-mb-2').removeClass('mb-2')
$('.js-ad-dependant-mb-3').removeClass('mb-3')
$('.js-ad-dependant-mb-5-reverse').addClass('mb-5')
return false
}
}

@ -3,7 +3,12 @@ import numeral from 'numeral'
import { BigNumber } from 'bignumber.js'
export function formatUsdValue (value) {
return `${formatCurrencyValue(value)} USD`
const formattedValue = formatCurrencyValue(value)
if (formattedValue === 'N/A') {
return formattedValue
} else {
return `${formattedValue} USD`
}
}
function formatTokenUsdValue (value) {
@ -12,8 +17,8 @@ function formatTokenUsdValue (value) {
export function formatCurrencyValue (value, symbol) {
symbol = symbol || '$'
if (isNaN(value) || value === '0') return 'N/A'
if (value === 0) return `${symbol}0.000000`
if (isNaN(value)) return 'N/A'
if (value === 0 || value === '0') return `${symbol}0.00`
if (value < 0.000001) return `Less than ${symbol}0.000001`
if (value < 1) return `${symbol}${numeral(value).format('0.000000')}`
if (value < 100000) return `${symbol}${numeral(value).format('0,0.00')}`
@ -49,7 +54,10 @@ function tryUpdateCalculatedUsdValues (el, usdExchangeRate = el.dataset.usdExcha
const ether = weiToEther(el.dataset.weiValue)
const usd = etherToUSD(ether, usdExchangeRate)
const formattedUsd = formatUsdValue(usd)
if (formattedUsd !== el.innerHTML) el.innerHTML = formattedUsd
if (formattedUsd !== el.innerHTML) {
$(el).data('rawUsdValue', usd)
el.innerHTML = formattedUsd
}
}
function tryUpdateUnitPriceValues (el, usdUnitPrice = el.dataset.usdUnitPrice) {

@ -23,8 +23,8 @@ export function prepareMethodArgs ($functionInputs, inputs) {
const inputType = inputs[ind] && inputs[ind].type
const inputComponents = inputs[ind] && inputs[ind].components
let sanitizedInputValue
sanitizedInputValue = replaceSpaces(inputValue, inputType, inputComponents)
sanitizedInputValue = replaceDoubleQuotes(sanitizedInputValue, inputType, inputComponents)
sanitizedInputValue = replaceDoubleQuotes(inputValue, inputType, inputComponents)
sanitizedInputValue = replaceSpaces(sanitizedInputValue, inputType, inputComponents)
if (isArrayInputType(inputType) || isTupleInputType(inputType)) {
if (sanitizedInputValue === '' || sanitizedInputValue === '[]') {
@ -38,6 +38,7 @@ export function prepareMethodArgs ($functionInputs, inputs) {
const elementInputType = inputType.split('[')[0]
var sanitizedElementValue = replaceDoubleQuotes(elementValue, elementInputType)
sanitizedElementValue = replaceSpaces(sanitizedElementValue, elementInputType)
if (isBoolInputType(elementInputType)) {
sanitizedElementValue = convertToBool(elementValue)
@ -141,7 +142,7 @@ function replaceSpaces (value, type, components) {
})
.join(',')
} else {
return value
return value.trim()
}
}

@ -1,5 +1,5 @@
import $ from 'jquery'
import { formatAllUsdValues } from './currency'
import { formatAllUsdValues, formatUsdValue } from './currency'
import { TokenBalanceDropdownSearch } from './token_balance_dropdown_search'
const tokenBalanceDropdown = (element) => {
@ -12,6 +12,15 @@ const tokenBalanceDropdown = (element) => {
.done(response => {
const responseHtml = formatAllUsdValues($(response))
$element.html(responseHtml)
const tokensCount = $('[data-dropdown-token-balance-test]').length
const $addressTokenWorth = $('[data-test="address-tokens-worth"]')
const tokensDsName = (tokensCount > 1) ? ' tokens' : ' token'
$('[data-test="address-tokens-panel-tokens-worth"]').text(`${$addressTokenWorth.text()} | ${tokensCount} ${tokensDsName}`)
const $addressTokensPanelNativeWorth = $('[data-test="address-tokens-panel-native-worth"]')
const rawUsdValue = $addressTokensPanelNativeWorth.children('span').data('raw-usd-value')
const rawUsdTokensValue = $addressTokenWorth.data('usd-value')
const formattedFullUsdValue = formatUsdValue(parseFloat(rawUsdValue) + parseFloat(rawUsdTokensValue))
$('[data-test="address-tokens-panel-net-worth"]').text(formattedFullUsdValue)
})
.fail(() => {
$loading.hide()

@ -24,6 +24,7 @@ export const initialState = {
balanceCard: null,
fetchedCoinBalanceBlockNumber: null,
transactionCount: null,
tokenTransferCount: null,
gasUsageCount: null,
validationCount: null,
countersFetched: false
@ -45,6 +46,7 @@ export function reducer (state = initialState, action) {
case 'COUNTERS_FETCHED': {
return Object.assign({}, state, {
transactionCount: action.transactionCount,
tokenTransferCount: action.tokenTransferCount,
gasUsageCount: action.gasUsageCount,
validationCount: action.validationCount,
countersFetched: true
@ -63,6 +65,13 @@ export function reducer (state = initialState, action) {
return Object.assign({}, state, { transactionCount })
}
case 'RECEIVED_NEW_TOKEN_TRANSFER': {
if (state.channelDisconnected) return state
const tokenTransferCount = (action.msg.fromAddressHash === state.addressHash) ? state.tokenTransferCount + 1 : state.tokenTransferCount
return Object.assign({}, state, { tokenTransferCount })
}
case 'RECEIVED_UPDATED_BALANCE': {
return Object.assign({}, state, {
balanceCard: action.msg.balanceCard,
@ -109,12 +118,30 @@ const elements = {
render ($el, state, oldState) {
if (state.countersFetched && state.transactionCount) {
if (oldState.transactionCount === state.transactionCount) return
$el.empty().append(numeral(state.transactionCount).format() + ' Transactions')
const transactionsDSName = (state.transactionCount > 1) ? ' Transactions' : ' Transaction'
$el.empty().append(numeral(state.transactionCount).format() + transactionsDSName)
$el.show()
$el.parent('.address-detail-item').removeAttr('style')
$('.address-transactions-count-item').removeAttr('style')
} else {
$el.hide()
$el.parent('.address-detail-item').css('display', 'none')
$('.address-transactions-count-item').css('display', 'none')
}
}
},
'[data-selector="transfer-count"]': {
load ($el) {
return { tokenTransferCount: numeral($el.text()).value() }
},
render ($el, state, oldState) {
if (state.countersFetched && state.tokenTransferCount) {
if (oldState.tokenTransferCount === state.tokenTransferCount) return
const transfersDSName = (state.tokenTransferCount > 1) ? ' Transfers' : ' Transfer'
$el.empty().append(numeral(state.tokenTransferCount).format() + transfersDSName)
$el.show()
$('.address-transfers-count-item').removeAttr('style')
} else {
$el.hide()
$('.address-transfers-count-item').css('display', 'none')
}
}
},
@ -125,12 +152,12 @@ const elements = {
render ($el, state, oldState) {
if (state.countersFetched && state.gasUsageCount) {
if (oldState.gasUsageCount === state.gasUsageCount) return
$el.empty().append(numeral(state.gasUsageCount).format() + ' Gas used')
$el.empty().append(numeral(state.gasUsageCount).format())
$el.show()
$el.parent('.address-detail-item').removeAttr('style')
$('.address-gas-used-item').removeAttr('style')
} else {
$el.hide()
$el.parent('.address-detail-item').css('display', 'none')
$('.address-gas-used-item').css('display', 'none')
}
}
},
@ -150,10 +177,10 @@ const elements = {
render ($el, state, oldState) {
if (state.countersFetched && state.validationCount) {
if (oldState.validationCount === state.validationCount) return
$el.empty().append(numeral(state.validationCount).format() + ' Blocks Validated')
$el.show()
$el.empty().append(numeral(state.validationCount).format())
$('.address-validation-count-item').removeAttr('style')
} else {
$el.hide()
$('.address-validation-count-item').css('display', 'none')
}
}
}
@ -202,6 +229,12 @@ if ($addressDetailsPage.length) {
msg: humps.camelizeKeys(msg)
})
})
addressChannel.on('transfer', (msg) => {
store.dispatch({
type: 'RECEIVED_NEW_TOKEN_TRANSFER',
msg: humps.camelizeKeys(msg)
})
})
const blocksChannel = socket.channel(`blocks:${addressHash}`, {})
blocksChannel.join()

@ -82,6 +82,7 @@
},
"../../../deps/phoenix": {
"version": "1.5.13",
"integrity": "sha512-aJKbnuCTtgH8qT7AzHhPwhOP3bqhja3ogFMQmUdY7jNzl/91nsUtpnz0M3HDW7j+IvfjiM2/TIkOaGwzW9BKhg==",
"license": "MIT"
},
"../../../deps/phoenix_html": {
@ -13254,15 +13255,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/pikaday": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz",
@ -28927,12 +28919,6 @@
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
"pikaday": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz",

@ -105,7 +105,9 @@ const appJs =
'banner': './js/lib/banner.js',
'autocomplete': './js/lib/autocomplete.js',
'search-results': './js/pages/search-results/search.js',
'token-overview': './js/pages/token/overview.js'
'token-overview': './js/pages/token/overview.js',
'export-csv': './css/export-csv.scss',
'datepicker': './js/lib/datepicker.js'
},
output: {
filename: '[name].js',

@ -217,6 +217,10 @@ defmodule BlockScoutWeb.Chain do
[paging_options: %{@default_paging_options | key: {total_gas, address_hash}}]
end
def paging_options(%{"token_name" => name, "token_type" => type, "value" => value}) do
[paging_options: %{@default_paging_options | key: {name, type, value}}]
end
def paging_options(_params), do: [paging_options: @default_paging_options]
def put_key_value_to_paging_options([paging_options: paging_options], key, value) do
@ -326,6 +330,10 @@ defmodule BlockScoutWeb.Chain do
%{"address_hash" => to_string(address_hash), "value" => Decimal.to_integer(value)}
end
defp paging_params({%CurrentTokenBalance{value: value}, _, %Token{name: name, type: type}}) do
%{"token_name" => name, "token_type" => type, "value" => Decimal.to_integer(value)}
end
defp paging_params(%CoinBalance{block_number: block_number}) do
%{"block_number" => block_number}
end

@ -200,7 +200,7 @@ defmodule BlockScoutWeb.AddressChannel do
rendered =
View.render_to_string(
AddressView,
"_balance_card.html",
"_balance_dropdown.html",
conn: socket,
address: address,
coin_balance_status: :current,

@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.{AccessHelpers, AddressView, Controller}
alias Explorer.Counters.{AddressTransactionsCounter, AddressTransactionsGasUsageCounter}
alias Explorer.Counters.{AddressTokenTransfersCounter, AddressTransactionsCounter, AddressTransactionsGasUsageCounter}
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Phoenix.View
@ -84,10 +84,11 @@ defmodule BlockScoutWeb.AddressController do
def address_counters(conn, %{"id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
{transaction_count, gas_usage_count, validation_count} = transaction_and_validation_count(address)
{transaction_count, token_transfer_count, gas_usage_count, validation_count} = address_counters(address)
json(conn, %{
transaction_count: transaction_count,
token_transfer_count: token_transfer_count,
gas_usage_count: gas_usage_count,
validation_count: validation_count
})
@ -95,18 +96,24 @@ defmodule BlockScoutWeb.AddressController do
_ ->
json(conn, %{
transaction_count: 0,
token_transfer_count: 0,
gas_usage_count: 0,
validation_count: 0
})
end
end
defp transaction_and_validation_count(address) do
defp address_counters(address) do
transaction_count_task =
Task.async(fn ->
transaction_count(address)
end)
token_transfer_count_task =
Task.async(fn ->
token_transfers_count(address)
end)
gas_usage_count_task =
Task.async(fn ->
gas_usage_count(address)
@ -117,7 +124,7 @@ defmodule BlockScoutWeb.AddressController do
validation_count(address)
end)
[transaction_count_task, gas_usage_count_task, validation_count_task]
[transaction_count_task, token_transfer_count_task, gas_usage_count_task, validation_count_task]
|> Task.yield_many(:timer.seconds(60))
|> Enum.map(fn {_task, res} ->
case res do
@ -138,6 +145,10 @@ defmodule BlockScoutWeb.AddressController do
AddressTransactionsCounter.fetch(address)
end
def token_transfers_count(address) do
AddressTokenTransfersCounter.fetch(address)
end
def gas_usage_count(address) do
AddressTransactionsGasUsageCounter.fetch(address)
end

@ -26,12 +26,12 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do
token_balances_except_bridged =
token_balances
|> Enum.filter(fn {token_balance, _} -> !token_balance.token.bridged end)
|> Enum.filter(fn {token_balance, _, _} -> !token_balance.token.bridged end)
circles_total_balance =
if Enum.count(circles_addresses_list) > 0 do
token_balances_except_bridged
|> Enum.reduce(Decimal.new(0), fn {token_balance, _}, acc_balance ->
|> Enum.reduce(Decimal.new(0), fn {token_balance, _, _}, acc_balance ->
{:ok, token_address} = Chain.hash_to_address(token_balance.address_hash)
from_address = from_address_hash(token_address)
@ -60,7 +60,8 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do
|> render("_token_balances.html",
address_hash: Address.checksum(address_hash),
token_balances: token_balances_with_price,
circles_total_balance: circles_total_balance
circles_total_balance: circles_total_balance,
conn: conn
)
_ ->
@ -70,7 +71,8 @@ defmodule BlockScoutWeb.AddressTokenBalanceController do
|> render("_token_balances.html",
address_hash: Address.checksum(address_hash),
token_balances: [],
circles_total_balance: Decimal.new(0)
circles_total_balance: Decimal.new(0),
conn: conn
)
end
else

@ -14,8 +14,12 @@ defmodule BlockScoutWeb.AddressTokenController do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash, [], false),
{:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params) do
tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params))
{tokens, next_page} = split_list_by_page(tokens_plus_one)
token_balances_plus_one =
address_hash
|> Chain.fetch_last_token_balances(paging_options(params))
|> Market.add_price()
{tokens, next_page} = split_list_by_page(token_balances_plus_one)
next_page_path =
case next_page_params(next_page, tokens, params) do
@ -29,11 +33,13 @@ defmodule BlockScoutWeb.AddressTokenController do
items =
tokens
|> Market.add_price()
|> Enum.map(fn token ->
|> Enum.map(fn {token_balance, bridged_token, token} ->
View.render_to_string(
AddressTokenView,
"_tokens.html",
token_balance: token_balance,
token: token,
bridged_token: bridged_token,
address: address,
conn: conn
)

@ -0,0 +1,16 @@
<div
class="address-current-balance"
data-token-balance-dropdown
data-api_path="<%= AccessHelpers.get_path(@conn, :address_token_balance_path, :index, Address.checksum(@address.hash)) %>"
>
<span data-loading class="mb-0">
<span class="loading-spinner-small mr-2">
<span class="loading-spinner-block-1"></span>
<span class="loading-spinner-block-2"></span>
</span>
<%= gettext("Fetching tokens...") %>
</span>
<span data-error-message class="mb-0" style="display: none;">
<%= gettext("Error trying to fetch balances.") %>
</span>
</div>

@ -9,7 +9,7 @@
</span>
<% else %>
<%= link to: address_path(BlockScoutWeb.Endpoint, :show, @address), "data-test": "address_hash_link", class: assigns[:class] do %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address: @address, contract: @contract, truncate: assigns[:truncate], use_custom_tooltip: @use_custom_tooltip, trimmed: if assigns[:trimmed], do: @trimmed, else: false %>
<%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address: @address, contract: @contract, truncate: assigns[:truncate], use_custom_tooltip: @use_custom_tooltip, no_tooltip: assigns[:no_tooltip], trimmed: if assigns[:trimmed], do: @trimmed, else: false %>
<% end %>
<% end %>
<% end %>

@ -1,14 +1,24 @@
<span class="<%= if @contract do %>contract-address<% end %>" data-address-hash="<%= @address %>">
<%= if name = primary_name(@address) do %>
<%= if @use_custom_tooltip == true do %>
<span><%= name %> (<%= short_hash(@address) %>...)</span>
<% else %>
<span data-toggle="tooltip" data-placement="top" title="<%= @address %>">
<%= if assigns[:no_tooltip] do %>
<%= if @use_custom_tooltip == true do %>
<span><%= name %> (<%= short_hash(@address) %>...)</span>
<% else %>
<span class="d-none d-md-none d-lg-inline d-xl-inline"><%= short_contract_name(name, 30) %></span>
<span class="d-inline d-md-inline d-lg-none d-xl-none"><%= short_contract_name(name, 10) %></span>
<span> (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>)</span>
</span>
<% end %>
<% end %>
<% else %>
<%= if @use_custom_tooltip == true do %>
<span><%= name %> (<%= short_hash(@address) %>...)</span>
<% else %>
<span data-toggle="tooltip" data-placement="top" title="<%= @address %>">
<span class="d-none d-md-none d-lg-inline d-xl-inline"><%= short_contract_name(name, 30) %></span>
<span class="d-inline d-md-inline d-lg-none d-xl-none"><%= short_contract_name(name, 10) %></span>
<span> (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>)</span>
</span>
<% end %>
<% end %>
<% else %>
<%= if assigns[:truncate] do %>
<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>

@ -14,36 +14,37 @@
<thead>
<tr>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
&nbsp;
</div>
<div class="stakes-table-th-content">
&nbsp;
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Address
</div>
<div class="stakes-table-th-content">
Address
</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Balance
</div>
<div class="stakes-table-th-content">
Balance
</div>
</th>
<%= if balance_percentage_enabled?(@total_supply) do %>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Percentage
</div>
</th>
<% end %>
<%= if balance_percentage_enabled?(@total_supply) do %>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Percentage
</div>
</th>
<% end %>
<th class="stakes-table-th">
<div class="stakes-table-th-content">
Txn Count
</div>
<div class="stakes-table-th-content">
Txn Count
</div>
</th>
</tr>
</thead>
<tbody data-items data-selector="top-addresses-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", total_supply: @total_supply %>
<% columns_num = if balance_percentage_enabled?(@total_supply), do: 5, else: 4 %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: columns_num %>
</tbody>
</table>
</div>

@ -9,11 +9,11 @@
<% circles_addresses_list = CustomContractsHelpers.get_custom_addresses_list(:circles_addresses) %>
<% current_address = "0x" <> Base.encode16(@address.hash.bytes, case: :lower) %>
<% created_from_address_hash = if from_address_hash(@address), do: "0x" <> Base.encode16(from_address_hash(@address).bytes, case: :lower), else: nil %>
<div class="row">
<div class="row js-ad-dependant-mb-2 js-ad-dependant-mb-5-reverse">
<!-- Address details -->
<div class="card-section col-md-12 col-lg-8 pr-0-md js-ad-dependant-mb-2">
<div class="col-md-12 js-ad-dependant-mb-2">
<div class="card js-ad-dependant-mb-2">
<div class="card-body">
<div class="card-body fs-14" style="line-height: 31px;">
<%= cond do %>
<% Enum.member?(dark_forest_addresses_list_0_4, current_address) -> %>
<%= render BlockScoutWeb.AddressView, "_custom_view_df_title.html", title: "zkSnark space warfare (v0.4)" %>
@ -38,8 +38,8 @@
<% true -> %>
<%= nil %>
<% end %>
<h1 class="card-title lg-card-title">
<div class="title-with-label"><%= gettext "Address Details" %></div>
<h1 class="card-title lg-card-title mb-2">
<div class="title-with-label"><%= address_title(@address) %> <%= gettext "Details" %></div>
<% validator_metadata = primary_validator_metadata(@address) %>
<%= if contract?(@address) do %>
<%= render BlockScoutWeb.FormView, "_tag.html", text: "contract", additional_classes: ["contract", "ml-1"] %>
@ -89,7 +89,7 @@
<% end %>
</span>
</h1>
<h3 class="address-detail-hash-title <%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
<h3 class="address-detail-hash-title mb-4 <%= if BlockScoutWeb.AddressView.contract?(@address) do %>contract-address<% end %>" data-test="address_detail_hash"><%= @address %></h3>
<%= if smart_contract_is_gnosis_safe_proxy?(@address) do %>
<span class="ml-2 mr-4">
<a data-test="external_url" href=https://xdai.gnosis-safe.io/app/#/safes/<%= @address.hash %>/transactions target="_blank">
@ -97,84 +97,220 @@
</a>
</span>
<% end %>
<!-- Verify in other explorers -->
<!--
<%# <%= render "_verify_other_explorers.html", hash: @address.hash, type: "address" %> %>
-->
<% address_name = primary_name(@address) %>
<%= cond do %>
<% @address.token -> %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Token name and symbol.") %>
<%= gettext("Token") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_token">
<%= link(
token_title(@address.token),
to: token_path(@conn,
:show,
@address.hash),
"data-test":
"token_hash_link"
)
%>
</dd>
</dl>
<% address_name -> %>
<%= if contract?(@address) do %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("The name found in the source code of the Contract.") %>
<%= gettext("Contract Name") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_contract">
<%= short_contract_name(address_name, 30) %>
</dd>
</dl>
<% else %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("The name of the validator.") %>
<%= gettext("Validator Name") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_contract">
<%= short_contract_name(address_name, 30) %>
</dd>
</dl>
<% end %>
<% true -> %>
<% end %>
<!-- Creator -->
<% from_address_hash = from_address_hash(@address) %>
<%= if contract?(@address) do %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Transactions and address of creation.") %>
<%= gettext("Creator") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_contract_creator">
<%= if from_address_hash do %>
<%= link(
trimmed_hash(from_address_hash(@address)),
to: address_path(@conn, :show, from_address_hash(@address))
) %>
<%= gettext "at" %>
<%= link(
trimmed_hash(transaction_hash(@address)),
to: transaction_path(@conn, :show, transaction_hash(@address)),
"data-test": "transaction_hash_link"
) %>
<% else %>
<p class="alert alert-danger" role="alert"><%= gettext("Error: Could not determine contract creator.") %></p>
<% end %>
</dd>
</dl>
<% end %>
<!-- Implementation -->
<%= if @is_proxy do %>
<% implementation_address = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %>
<br>
<%= gettext("Implementation:") %>
<%= link(
implementation_address,
to: address_path(@conn, :show, implementation_address),
class: "contract-address mb-2",
style: "margin-top: 0.5rem;"
)
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Implementation address of the proxy contract.") %>
<%= gettext("Implementation") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_contract_implementation">
<%= link(
implementation_address,
to: address_path(@conn, :show, implementation_address),
class: "contract-address mb-2",
style: "margin-top: 0.5rem;"
)
%>
</dd>
</dl>
<% end %>
<div class="d-flex flex-column flex-lg-row justify-content-start text-muted">
<%= if address_name = primary_name(@address) do %>
<strong class="mr-4 mb-2 text-dark" title="<%= address_name %>">
<%= short_contract_name(address_name, 30) %>
</strong>
<% end %>
<%= if @address.token do %>
<span class="mr-4 mb-2">
<%= link(token_title(@address.token), to: token_path(@conn, :show, @address.hash), "data-test": "token_hash_link" ) %>
</span>
<% end %>
<span>
<span class="address-detail-item" style="display: none">
<span data-selector="transaction-count">
</span>
</span>
<span class="address-detail-item" style="display: none">
<span data-selector="gas-usage-count">
</span>
</span>
<%= if @address.fetched_coin_balance_block_number do %>
<span class="address-detail-item">
<%= gettext("Last Balance Update: Block #") %><span data-selector="fetched-coin-balance-block-number"><%= @address.fetched_coin_balance_block_number %></span>
<!-- Balance -->
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens).") %>
<%= gettext("Balance") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_balance">
<%= balance(@address) %>
<%= if !match?({:pending, _}, @coin_balance_status) && !empty_exchange_rate?(@exchange_rate) do %>
<% usd_value = to_string(@exchange_rate.usd_value) %>
<span class="address-current-balance">
(<span
data-wei-value="<%= if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>"
data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"
data-placement="top"
data-toggle="tooltip"
data-html="true"
title='<%= "@ " <> usd_value <> "/" <> gettext("Ether") %>'
>
</span>)
</span>
<% end %>
<span class="address-detail-item">
<span data-selector="validation-count">
</span>
</dd>
</dl>
<!-- Tokens -->
<dl class="row" data-test="outside_of_dropdown">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("All tokens in the account and total value.") %>
<%= gettext("Tokens") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_tokens" data-selector="balance-card">
<%= render BlockScoutWeb.AddressView, "_balance_dropdown.html", conn: @conn, address: @address %>
</dd>
</dl>
<!-- Transaction count -->
<dl class="row address-transactions-count-item">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Number of transactions related to this address.") %>
<%= gettext("Transactions") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_transaction_count">
<%= if @conn.request_path |> String.contains?("/transactions") do %>
<a href="#txs" class="page-link bs-label large btn-no-border-link-to-tems" data-selector="transaction-count">
</a>
<% else %>
<a href="<%= AccessHelpers.get_path(@conn, :address_transaction_path, :index, @address.hash)%>#txs" class="page-link bs-label large btn-no-border-link-to-tems" data-selector="transaction-count">
</a>
<% end %>
</dd>
</dl>
<!-- Transfers count -->
<dl class="row address-transfers-count-item">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Number of transfers to/from this address.") %>
<%= gettext("Transfers") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_transfer_count">
<%= if @conn.request_path |> String.contains?("/token-transfers") do %>
<a href="#transfers" class="page-link bs-label large btn-no-border-link-to-tems" data-selector="transfer-count">
</a>
<% else %>
<a href="<%= AccessHelpers.get_path(@conn, :address_token_transfers_path, :index, @address.hash)%>#transfers" class="page-link bs-label large btn-no-border-link-to-tems" data-selector="transfer-count">
</a>
<% end %>
</dd>
</dl>
<!-- Gas used -->
<dl class="row address-gas-used-item">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Gas used by the address.") %>
<%= gettext("Gas Used") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_gas_used">
<span data-selector="gas-usage-count">
</span>
</span>
</div>
<% from_address_hash = from_address_hash(@address) %>
<%= if contract?(@address) do %>
<span class="text-muted" data-test="address_contract_creator">
<%= if from_address_hash do %>
<%= gettext "Created by" %> <%= link(
trimmed_hash(from_address_hash(@address)),
to: address_path(@conn, :show, from_address_hash(@address))
) %>
<%= gettext "at" %>
</dd>
</dl>
<!-- Last balance update -->
<%= if @address.fetched_coin_balance_block_number do %>
<dl class="row">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Block number in which the address was updated.") %>
<%= gettext("Last Balance Update") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_last_balance_update">
<%= link(
trimmed_hash(transaction_hash(@address)),
to: transaction_path(@conn, :show, transaction_hash(@address)),
"data-test": "transaction_hash_link"
@address.fetched_coin_balance_block_number,
to: block_path(@conn, :show, @address.fetched_coin_balance_block_number),
class: "tile-title-lg"
) %>
<% else %>
<p class="alert alert-danger" role="alert"><%= gettext("Error: Could not determine contract creator.") %></p>
<% end %>
</span>
</dd>
</dl>
<% end %>
<!-- Verify in other explorers -->
<!--
<%# <%= render "_verify_other_explorers.html", hash: @address.hash, type: "address" %> %>
-->
<!-- Blocks Validated -->
<dl class="row address-validation-count-item" style="display: none;">
<dt class="col-sm-4 col-md-4 col-lg-3 text-muted">
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html",
text: gettext("Gas used by the address.") %>
<%= gettext("Blocks Validated") %>
</dt>
<dd class="col-sm-8 col-md-8 col-lg-9" data-test="address_blocks_validated">
<span data-selector="validation-count">
</span>
</dd>
</dl>
</div>
</div>
</div>
<!-- Balance -->
<div class="card-section col-md-12 col-lg-4 pl-0-md js-ad-dependant-mb-2" data-selector="balance-card">
<%= render BlockScoutWeb.AddressView, "_balance_card.html", conn: @conn, address: @address, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status %>
</div>
</div>
</section>

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

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

@ -1,22 +1,69 @@
<div class="tile tile-type-token">
<div class="row justify-content align-items-center">
<div class="col-md-7 d-flex flex-column mt-3 mt-md-0">
<%= link(
to: address_token_transfers_path(@conn, :index, to_string(@address.hash), to_string(@token.contract_address_hash)),
class: "tile-title-lg",
"data-test": "token_transfers_#{@token.contract_address_hash}"
) do %>
<%= token_name(@token) %>
<tr data-identifier-hash="<%= @token.contract_address_hash %>">
<td class="stakes-td">
<!-- incremented number by order in the list -->
<span class="color-lighten">
</span>
</td>
<td class="stakes-td">
<span
class="token-icon mr-1"
>
<%= if System.get_env("DISPLAY_TOKEN_ICONS") === "true" do %>
<% chain_id_for_token_icon = if @bridged_token && @bridged_token.foreign_chain_id, do: @bridged_token.foreign_chain_id |> Decimal.to_integer() |> to_string(), else: System.get_env("CHAIN_ID") %>
<% address_hash = if @bridged_token && @bridged_token.foreign_token_contract_address_hash, do: @bridged_token.foreign_token_contract_address_hash, else: @token.contract_address_hash %>
<% token_icon_url = Chain.get_token_icon_url_by(chain_id_for_token_icon, Address.checksum(address_hash)) %>
<%= if token_icon_url do %>
<img heigth=15 width=15 src="<%= token_icon_url %>" style="margin-top: -2px;"/>
<% end %>
<span><%= @token.type %> - <%= @token.contract_address_hash %></span>
<%= if @token.usd_value do %>
<span data-selector="token-price" data-usd-value="<%= @token.usd_value %>"></span>
<% end %>
</span>
<%= link(
to: address_token_transfers_path(@conn, :index, to_string(@address.hash), to_string(@token.contract_address_hash)),
class: "tile-title-lg",
"data-test": "token_transfers_#{@token_balance.token.contract_address_hash}"
) do %>
<%= token_name(@token) %>
<% end %>
</td>
<td class="stakes-td">
<%= @token.type %>
</td>
<td class="stakes-td">
<%= format_according_to_decimals(@token_balance.value, @token.decimals) %>
</td>
<td class="stakes-td">
<%= @token.symbol %>
</td>
<td class="stakes-td">
<p class="mb-0 col-md-6 text-right text-muted">
<% token_price = if @token_balance.token.usd_value, do: @token_balance.token.usd_value, else: nil %>
<span data-selector="token-price" data-token-usd-value="<%= @token_balance.token.usd_value %>"><%= ChainView.format_currency_value(token_price, "@") %></span>
</p>
</td>
<td class="stakes-td">
<%= if @bridged_token && @bridged_token.lp_token && @bridged_token.custom_cap do %>
<% lp_token_balance_usd = @token_balance.value |> Decimal.div(@token_balance.token.total_supply) |> Decimal.mult(@bridged_token.custom_cap) |> Decimal.round(4) %>
<p class="mb-0 col-md-6 text-right">
<span data-selector="token-balance-usd" data-usd-value="<%= lp_token_balance_usd %>"><%= ChainView.format_usd_value(lp_token_balance_usd) %></span>
</p>
<% else %>
<%= if @token_balance.token.usd_value do %>
<p class="mb-0 col-md-6 text-right">
<span data-selector="token-balance-usd" data-usd-value="<%= Chain.balance_in_usd(@token_balance) %>"><%= ChainView.format_usd_value(Chain.balance_in_usd(@token_balance)) %></span>
</p>
<% end %>
</div>
<div class="col-md-5 d-flex flex-column text-md-right mt-3 mt-md-0">
<span class="tile-title-lg text-md-right align-bottom">
<%= format_according_to_decimals(@token.balance, @token.decimals) %> <%= @token.symbol %>
</span>
</div>
</div>
</div>
<% end %>
</td>
<td class="stakes-td">
<%= with {:ok, address} <- Chain.hash_to_address(@token.contract_address_hash) do
render BlockScoutWeb.AddressView,
"_link.html",
address: address,
contract: false,
use_custom_tooltip: false,
no_tooltip: true
end
%>
</td>
</tr>

@ -3,16 +3,56 @@
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<section>
<section id="tokens">
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h2 class="card-title list-title-description"><%= gettext "Tokens" %></h2>
<%= render BlockScoutWeb.AddressTokenView,
"overview.html",
address: @address,
exchange_rate: @exchange_rate
%>
<div class="list-top-pagination-container-wrapper">
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
<div class="stakes-table-container">
<table style="width: 100%;">
<thead>
<tr>
<th class="stakes-table-th">
<div class="stakes-table-th-content">&nbsp;</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Asset</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Type</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Amount</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Symbol</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Price</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Value</div>
</th>
<th class="stakes-table-th">
<div class="stakes-table-th-content">Contract Address</div>
</th>
</tr>
</thead>
<tbody data-items data-selector="address-tokens-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 8 %>
</tbody>
</table>
</div>
<button data-error-message class="alert alert-danger col-12 text-left" style="display: none;">
<span href="#" class="alert-link"><%= gettext("Something went wrong, click to reload.") %></span>
</button>
@ -23,20 +63,7 @@
</div>
</div>
<div data-items>
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<div class="transaction-bottom-panel">
<div csv-download class="download-all-transactions">
Download <a class="download-all-transactions-link" href=<%= csv_export_path(@conn, :index, %{"address" => Address.checksum(@address.hash), "type" => "token-transfers"}) %>><%= gettext("CSV") %></span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16">
<path fill="#333333" fill-rule="evenodd" d="M13 16H1c-.999 0-1-1-1-1V1s-.004-1 1-1h6l7 7v8s-.032 1-1 1zm-1-8c0-.99-1-1-1-1H8s-1 .001-1-1V3c0-.999-1-1-1-1H2v12h10V8z"/>
</svg>
</a>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>

@ -0,0 +1,64 @@
<% native_coin = gettext("ETH") %>
<!-- USD value of the balance -->
<% wei_value = if @address.fetched_coin_balance, do: @address.fetched_coin_balance.value %>
<% raw_usd_value =
case @exchange_rate.usd_value do
%Decimal{} ->
if wei_value do
%Wei{value: Decimal.new(wei_value)}
|> Wei.to(:ether)
|> Decimal.mult(@exchange_rate.usd_value)
else
Decimal.new(0)
end
_ -> Decimal.new(0)
end
%>
<% data_usd_exchange_rate =
unless AddressView.empty_exchange_rate?(@exchange_rate) do
"data-usd-exchange-rate='#{@exchange_rate.usd_value}' data-raw-usd-value='#{raw_usd_value}'"
end
%>
<% native_coin_balance_token = AddressView.balance(@address)
%>
<% native_coin_balance_usd =
if AddressView.empty_exchange_rate?(@exchange_rate) do
nil
else
"<span
data-wei-value='#{wei_value}'
#{data_usd_exchange_rate}
>
</span>"
end
%>
<% native_coin_balance =
if native_coin_balance_usd do
native_coin_balance_usd <> " | " <> native_coin_balance_token
else
native_coin_balance_token
end
%>
<div class="d-md-flex">
<%= render BlockScoutWeb.AddressTokenView, "overview_item.html",
title: gettext("Net Worth"),
tooltip: gettext("Shows total assets held in the address"),
value: "N/A",
data_test: "address-tokens-panel-net-worth",
classes: ["fs-14"]
%>
<%= render BlockScoutWeb.AddressTokenView, "overview_item.html",
title: "#{native_coin} #{gettext("Balance")}",
tooltip: "#{gettext("Shows the current")} #{native_coin} #{gettext("balance of the address")}",
value: raw(native_coin_balance),
data_test: "address-tokens-panel-native-worth",
classes: ["fs-14"]
%>
<%= render BlockScoutWeb.AddressTokenView, "overview_item.html",
title: gettext("Tokens"),
tooltip: gettext("Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)."),
value: "N/A",
data_test: "address-tokens-panel-tokens-worth",
classes: ["fs-14"]
%>
</div>

@ -0,0 +1,14 @@
<div class="d-flex mr-4" style="padding: 10px;">
<div>
<div class="d-flex">
<h1 class="card-title mb-2"><%= @title %></h1>
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip.html",
text: @tooltip,
additional_classes: ["ml-2"]
%>
</div>
<div data-test="<%= if assigns[:data_test], do: @data_test %>" class="<%= if assigns[:classes] do @classes |> Enum.join(" ") end %>"">
<%= @value %>
</div>
</div>
</div>

@ -1,66 +1,81 @@
<%= if Decimal.cmp(@circles_total_balance, 0) == :gt do %>
<p class="address-current-balance"><%= format_according_to_decimals(@circles_total_balance, Decimal.new(18)) %> CRC</p>
<% end %>
<%= if Enum.any?(@token_balances) do %>
<a
href="#"
data-dropdown-toggle
data-toggle="dropdown"
role="button"
id="dropdown-tokens"
aria-haspopup="true"
aria-expanded="false"
style="text-decoration: none;">
<div class="d-flex">
<%= if Decimal.cmp(@circles_total_balance, 0) == :gt do %>
<p class="address-current-balance"><%= format_according_to_decimals(@circles_total_balance, Decimal.new(18)) %> CRC</p>
<% end %>
<%= if Enum.any?(@token_balances) do %>
<a
href="#"
data-dropdown-toggle
data-toggle="dropdown"
role="button"
id="dropdown-tokens"
aria-haspopup="true"
aria-expanded="false"
class="btn-dropdown-line basic dropdown-toggle mr-1"
style="text-decoration: none; height: 31px; padding: 0 10px;">
<i class="fas fa-chevron-down mr-2"></i>
<span data-tokens-count><%= tokens_count_title(@token_balances) %></span>
<%= if @token_balances && Decimal.cmp(address_tokens_usd_sum_cache(@address_hash, @token_balances), Decimal.new(0)) == :gt do %>
(<span data-usd-value=<%= address_tokens_usd_sum_cache(@address_hash, @token_balances) %> ></span>)
<% end %>
</a>
<% else %>
<span data-tokens-count><%= tokens_count_title(@token_balances) %></span>
<% end %>
<div class="dropdown-menu dropdown-menu-right token-balance-dropdown p-0" aria-labelledby="dropdown-tokens">
<div data-dropdown-items class="dropdown-items">
<div class="position-relative">
<svg class="position-absolute dropdown-search-icon" viewBox="0 0 16 17" xmlns="http://www.w3.org/2000/svg" width="16" height="17" class="dropdown-search-icon position-absolute">
<path fill="#7DD79F" fill-rule="evenodd" d="M15.713 15.727a.982.982 0 0 1-1.388 0l-2.289-2.29C10.773 14.403 9.213 15 7.5 15A7.5 7.5 0 1 1 15 7.5c0 1.719-.602 3.284-1.575 4.55l2.288 2.288a.983.983 0 0 1 0 1.389zM7.5 2a5.5 5.5 0 1 0 0 11 5.5 5.5 0 1 0 0-11z"></path>
</svg>
<%= text_input(
:token_search,
:name,
class: "w-100 dropdown-search-field",
'data-filter-dropdown-tokens': true,
placeholder: gettext("Search tokens")
) %>
<span data-tokens-count><%= tokens_count_title(@token_balances) %></span>
</a>
<div class="mr-3" style="line-height: 31px;">
(<span data-test="address-tokens-worth" data-usd-value=<%= address_tokens_usd_sum_cache(@address_hash, @token_balances) %>></span>)
</div>
<%= if Enum.any?(@token_balances, fn {token_balance, _} -> token_balance.token.type == "ERC-721" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-721"),
type: "ERC-721"
) %>
<%= if @conn.request_path |> String.contains?("/tokens") do %>
<a href="#tokens" style="text-decoration: none;">
<span class="btn-wallet-icon">
<i class="fas fa-wallet " style="line-height: 31px;"></i>
</span>
</a>
<% else %>
<a href="<%= AccessHelpers.get_path(@conn, :address_token_path, :index, @address_hash) %>#tokens" style="text-decoration: none;">
<span class="btn-wallet-icon">
<i class="fas fa-wallet" style="line-height: 31px;"></i>
</span>
</a>
<% end %>
<% else %>
<span data-tokens-count style="line-height: 31px;"><%= tokens_count_title(@token_balances) %></span>
<% end %>
<%= if Enum.any?(@token_balances, fn {token_balance, _} -> token_balance.token.type == "ERC-1155" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-1155"),
type: "ERC-1155"
) %>
<% end %>
<div class="dropdown-menu dropdown-menu-right token-balance-dropdown p-0" aria-labelledby="dropdown-tokens">
<div data-dropdown-items class="dropdown-items">
<div class="position-relative">
<svg class="position-absolute dropdown-search-icon" viewBox="0 0 16 17" xmlns="http://www.w3.org/2000/svg" width="16" height="17" class="dropdown-search-icon position-absolute">
<path fill="#7DD79F" fill-rule="evenodd" d="M15.713 15.727a.982.982 0 0 1-1.388 0l-2.289-2.29C10.773 14.403 9.213 15 7.5 15A7.5 7.5 0 1 1 15 7.5c0 1.719-.602 3.284-1.575 4.55l2.288 2.288a.983.983 0 0 1 0 1.389zM7.5 2a5.5 5.5 0 1 0 0 11 5.5 5.5 0 1 0 0-11z"></path>
</svg>
<%= text_input(
:token_search,
:name,
class: "w-100 dropdown-search-field",
'data-filter-dropdown-tokens': true,
placeholder: gettext("Search tokens")
) %>
</div>
<%= if Enum.any?(@token_balances, fn {token_balance, _, _} -> token_balance.token.type == "ERC-1155" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-1155"),
type: "ERC-1155"
) %>
<% end %>
<%= if Enum.any?(@token_balances, fn {token_balance, _} -> token_balance.token.type == "ERC-20" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-20"),
type: "ERC-20"
) %>
<% end %>
<%= if Enum.any?(@token_balances, fn {token_balance, _, _} -> token_balance.token.type == "ERC-721" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-721"),
type: "ERC-721"
) %>
<% end %>
<%= if Enum.any?(@token_balances, fn {token_balance, _, _} -> token_balance.token.type == "ERC-20" end) do %>
<%= render(
"_tokens.html",
conn: @conn,
token_balances: filter_by_type(@token_balances, "ERC-20"),
type: "ERC-20"
) %>
<% end %>
</div>
</div>
</div>

@ -3,7 +3,7 @@
<%= @type %> (<span data-number-of-tokens-by-type="<%= @type %>"><%= Enum.count(@token_balances)%></span>)
</h6>
<%= for {token_balance, bridged_token} <- sort_by_usd_value_and_name(@token_balances) do %>
<%= for {token_balance, bridged_token, token} <- sort_by_usd_value_and_name(@token_balances) do %>
<div
class="border-bottom"
data-dropdown-token-balance-test
@ -21,13 +21,13 @@
class: "dropdown-item"
) do %>
<div class="row">
<p class="mb-0 col-md-6"><%= token_name(token_balance.token) %>
<p class="mb-0 col-md-6"><%= token_name(token) %>
<%= if bridged_token && bridged_token.custom_metadata do %>
<%= "(" <> bridged_token.custom_metadata <> ")" %>
<% end %>
</p>
<%= if bridged_token && bridged_token.lp_token && bridged_token.custom_cap do %>
<% lp_token_balance_usd = token_balance.value |> Decimal.div(token_balance.token.total_supply) |> Decimal.mult(bridged_token.custom_cap) |> Decimal.round(4) %>
<% lp_token_balance_usd = token_balance.value |> Decimal.div(token.total_supply) |> Decimal.mult(bridged_token.custom_cap) |> Decimal.round(4) %>
<p class="mb-0 col-md-6 text-right">
<span data-selector="token-balance-usd" data-usd-value="<%= lp_token_balance_usd %>"></span>
</p>

@ -3,7 +3,7 @@
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<section data-page="address-token-transfers">
<section data-page="address-token-transfers" id="transfers">
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<div data-async-load data-async-listing="<%= @current_path %>" class="card-body">
@ -63,7 +63,10 @@
<%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
<div class="transaction-bottom-panel">
<%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "token-transfers", conn: @conn %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>
</div>
</div>

@ -4,7 +4,7 @@
<%= render BlockScoutWeb.AddressView, "overview.html", address: @address, is_proxy: is_proxy, conn: @conn, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status, counters_path: @counters_path %>
<section data-page="address-transactions">
<section data-page="address-transactions" id="txs">
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
<div class="card-body" data-async-listing="<%= @current_path %>">
@ -65,15 +65,7 @@
</div>
<div class="transaction-bottom-panel">
<div class="download-all-transactions">
Download <a class="download-all-transactions-link" href=<%= csv_export_path(@conn, :index, %{"address" => Address.checksum(@address.hash), "type" => "transactions"}) %>><%= gettext("CSV") %></span>
<div class="csv-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16">
<path fill="#333333" fill-rule="evenodd" d="M13 16H1c-.999 0-1-1-1-1V1s-.004-1 1-1h6l7 7v8s-.032 1-1 1zm-1-8c0-.99-1-1-1-1H8s-1 .001-1-1V3c0-.999-1-1-1-1H2v12h10V8z"/>
</svg>
</div>
</a>
</div>
<%= render BlockScoutWeb.CommonComponentsView, "_csv_export_button.html", address: Address.checksum(@address.hash), type: "transactions", conn: @conn %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
</div>

@ -68,7 +68,7 @@
<!-- Gas Used -->
<div class="mr-3 mr-md-0">
<%= formatted_gas(@block.gas_used) %>
<% gas = if @block.gas_limit > 0, do: Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit), else: 0 %>
<% gas = if Decimal.cmp(@block.gas_limit, 0) == :gt, do: Decimal.to_integer(@block.gas_used) / Decimal.to_integer(@block.gas_limit), else: 0 %>
(<%= formatted_gas(gas, format: "#.#%") %>)
<%= gettext "Gas Used" %>
</div>

@ -6,7 +6,7 @@
<div class="col-md-12 js-ad-dependant-mb-2">
<!-- Block Details -->
<div class="card js-ad-dependant-mb-2">
<div class="card-body transaction-details">
<div class="card-body fs-14" style="line-height: 32px;">
<dl class="pagination-container">
<h1 class="card-title" data-test="detail_type">
<%= gettext("%{block_type} Details", block_type: block_type(@block)) %>
@ -74,7 +74,7 @@
<%= gettext("Transactions") %>
</dt>
<dd class="col-sm-9 col-lg-10">
<a href="#txs" class="page-link bs-label large btn-no-border-transactions">
<a href="#txs" class="page-link bs-label large btn-no-border-link-to-tems">
<%= if @block_transaction_count == 1 do %>
<%= gettext "%{count} Transaction", count: @block_transaction_count %>
<% else %>

@ -51,9 +51,9 @@
</thead>
<tbody data-items data-selector="top-bridged-tokens-list">
<%= if @destination == :eth do %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", is_eth_bridged_tokens_table: true %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 6 %>
<% else %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", is_bsc_bridged_tokens_table: true %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 5 %>
<% end %>
</tbody>
</table>

@ -190,7 +190,7 @@
<span class="dashboard-banner-network-stats-label">
<%= gettext "Total transactions" %>
</span>
<div style="display: flex;">
<div class="d-flex">
<span class="dashboard-banner-network-stats-value" data-selector="transaction-count">
<%= BlockScoutWeb.Cldr.Number.to_string!(@transaction_estimated_count, format: "#,###") %>
</span>

@ -0,0 +1,7 @@
<div csv-download class="download-all-transactions">
Download <a class="download-all-transactions-link" href=<%= csv_export_path(@conn, :index, %{"address" => @address, "type" => @type }) %>><%= gettext("CSV") %></span>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16">
<path fill="#333333" fill-rule="evenodd" d="M13 16H1c-.999 0-1-1-1-1V1s-.004-1 1-1h6l7 7v8s-.032 1-1 1zm-1-8c0-.99-1-1-1-1H8s-1 .001-1-1V3c0-.999-1-1-1-1H2v12h10V8z"/>
</svg>
</a>
</div>

@ -1,170 +1,9 @@
<%= for _r <- 1..5 do %>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:total_supply] do %>
<%= if balance_percentage_enabled?(@total_supply) do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<% end %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:is_eth_bridged_tokens_table] || assigns[:is_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<%= if assigns[:is_bsc_bridged_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:total_supply] do %>
<%= if balance_percentage_enabled?(@total_supply) do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<% end %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:is_eth_bridged_tokens_table] || assigns[:is_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<%= if assigns[:is_bsc_bridged_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:total_supply] do %>
<%= if balance_percentage_enabled?(@total_supply) do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<% end %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:is_eth_bridged_tokens_table] || assigns[:is_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<%= if assigns[:is_bsc_bridged_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:total_supply] do %>
<%= if balance_percentage_enabled?(@total_supply) do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<% end %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:is_eth_bridged_tokens_table] || assigns[:is_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<%= if assigns[:is_bsc_bridged_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
</tr>
<tr class="table-content-pseudo">
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:total_supply] do %>
<%= if balance_percentage_enabled?(@total_supply) do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<% end %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<%= if assigns[:is_eth_bridged_tokens_table] || assigns[:is_tokens_table] do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
<%= if assigns[:is_bsc_bridged_tokens_table] do %>
<%= for _c <- 1..@columns_num do %>
<td class="stakes-td">
<span class="table-content-loader"></span>
</td>
<% end %>
</tr>
<% end %>

@ -36,7 +36,7 @@
</tr>
</thead>
<tbody data-items data-selector="search-results-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html" %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 4 %>
</tbody>
</table>
</div>

@ -46,11 +46,11 @@
<span class="btn-full-primary-text"><%= gettext("Bridge STAKE to Ethereum") %></span>
</a>
<a
class="btn-full-primary full-width"
class="btn-full-primary full-width btn-add-full"
href="https://ascendex.com/register?inviteCode=5EYVQSTQ"
target="_blank"
>
<span class="btn-full-primary-text"><%= gettext("Trade STAKE on BitMax") %></span>
<span class="btn-full-primary-text"><%= gettext("Trade STAKE on AscendEX") %></span>
</a>
</div>
</div>

@ -9,7 +9,7 @@
<div class="card-body" data-async-load data-async-listing="<%= @current_path %>">
<h1 class="card-title list-title-description"><%= gettext "Tokens" %></h1>
<div class="list-top-pagination-container-wrapper tokens-list-search-input-outer-container" style="display: flex; float: right;">
<div class="list-top-pagination-container-wrapper tokens-list-search-input-outer-container d-flex" style="float: right;">
<label class="tokens-list-search-input-container tokens mr-3">
<input data-search-field="" class="form-control tokens-list-search-input search-input" type="text" name="filter" placeholder="Token name or symbol" id="search-text-input" />
</label>
@ -43,7 +43,7 @@
</tr>
</thead>
<tbody data-items data-selector="top-tokens-list">
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", is_tokens_table: true %>
<%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 5 %>
</tbody>
</table>
</div>

@ -20,7 +20,7 @@
<%= gettext "To have guaranteed accuracy, use the link above to verify the contract's source code." %>
<%= for {:ok, method_id, text, mapping} <- candidates do %>
<hr>
<hr/>
<h3><%= text %>: </h3>
<%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %>
@ -28,6 +28,13 @@
<% end %>
<% {:ok, method_id, text, mapping} -> %>
<%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %>
<% {:error, :contract_verified, candidates} -> %>
<h3><%= gettext "Potential matches from our contract method database:" %></h3>
<%= for {:ok, method_id, text, mapping} <- candidates do %>
<hr/>
<h3><%= text %>: </h3>
<%= render(BlockScoutWeb.TransactionView, "_decoded_input_body.html", method_id: method_id, text: text, mapping: mapping) %>
<% end %>
<% _ -> %>
<div class="alert alert-danger">
<%= gettext "Failed to decode input data." %>

@ -26,7 +26,7 @@
<% {:ok, created_from_address} = if to_address_hash, do: Chain.hash_to_address(to_address_hash), else: {:ok, nil} %>
<% created_from_address_hash_str = if from_address_hash(created_from_address), do: "0x" <> Base.encode16(from_address_hash(created_from_address).bytes, case: :lower), else: nil %>
<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %>
<section class="transaction-details" data-page="transaction-details" data-page-transaction-hash="<%= @transaction %>">
<section class="fs-14" data-page="transaction-details" data-page-transaction-hash="<%= @transaction %>">
<div class="row">
<div class="col-md-12">
<!-- Transaction Details -->

@ -4,9 +4,9 @@
<%= {:error, :contract_not_verified, _cadidates} -> %>
<div class="alert alert-info">
<%= gettext "To see accurate decoded input data, the contract must be verified." %>
<%= case @transaction do %>
<% %{to_address: %{hash: hash}} -> %>
<% path = address_verify_contract_path(@conn, :new, hash) %>
<%= case @log do %>
<% %{address_hash: %Explorer.Chain.Hash{} = address_hash} -> %>
<% path = address_verify_contract_path(@conn, :new, address_hash) %>
<%= gettext "Verify the contract " %><a href="<%= path %>"><%= gettext "here" %></a>
<% _ -> %>
<%= nil %>
@ -52,7 +52,7 @@
</table>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% {:error, :contract_not_verified, results} -> %>
<%= for {:ok, method_id, text, mapping} <- results do %>
<%= for {:ok, method_id, text, mapping} <- results do %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<table summary="Transaction Info" class="table thead-light table-bordered transaction-input-table">
@ -66,7 +66,23 @@
</tr>
</table>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% end %>
<% end %>
<% {:error, :contract_verified, results} -> %>
<%= for {:ok, method_id, text, mapping} <- results do %>
<dt class="col-md-2"><%= gettext "Decoded" %></dt>
<dd class="col-md-10">
<table summary="Transaction Info" class="table thead-light table-bordered transaction-input-table">
<tr>
<td>Method Id</td>
<td colspan="3"><code>0x<%= method_id %></code></td>
</tr>
<tr>
<td>Call</td>
<td colspan="3"><code><%= text %></code></td>
</tr>
</table>
<%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %>
<% end %>
<% _ -> %>
<%= nil %>
<% end %>

@ -1,6 +1,7 @@
defmodule BlockScoutWeb.AddressTokenBalanceView do
use BlockScoutWeb, :view
alias BlockScoutWeb.AccessHelpers
alias Explorer.Chain
alias Explorer.Counters.AddressTokenUsdSum
@ -9,7 +10,7 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do
end
def filter_by_type(token_balances, type) do
Enum.filter(token_balances, fn {token_balance, _} -> token_balance.token.type == type end)
Enum.filter(token_balances, fn {token_balance, _, _} -> token_balance.token.type == type end)
end
@doc """
@ -27,12 +28,12 @@ defmodule BlockScoutWeb.AddressTokenBalanceView do
"""
def sort_by_usd_value_and_name(token_balances) do
token_balances
|> Enum.sort(fn {token_balance1, _}, {token_balance2, _} ->
|> Enum.sort(fn {token_balance1, _, token1}, {token_balance2, _, token2} ->
usd_value1 = token_balance1.token.usd_value
usd_value2 = token_balance2.token.usd_value
token_name1 = token_balance1.token.name
token_name2 = token_balance2.token.name
token_name1 = token1.name
token_name2 = token2.name
sort_by_name = sort_2_tokens_by_name(token_name1, token_name2)

@ -2,6 +2,7 @@ defmodule BlockScoutWeb.AddressTokenTransferView do
use BlockScoutWeb, :view
alias BlockScoutWeb.AccessHelpers
alias Explorer.Chain.Address
def format_current_filter(filter) do
case filter do

@ -1,5 +1,7 @@
defmodule BlockScoutWeb.AddressTokenView do
use BlockScoutWeb, :view
alias Explorer.Chain.Address
alias BlockScoutWeb.{AddressView, ChainView}
alias Explorer.Chain
alias Explorer.Chain.{Address, Wei}
end

@ -1,7 +1,8 @@
defmodule BlockScoutWeb.ChainView do
use BlockScoutWeb, :view
import Number.Currency, only: [number_to_currency: 1, number_to_currency: 2]
require Decimal
import Number.Currency, only: [number_to_currency: 2]
alias BlockScoutWeb.LayoutView
alias Explorer.Chain.Cache.GasPriceOracle
@ -36,35 +37,53 @@ defmodule BlockScoutWeb.ChainView do
if System.get_env("SUPPLY_MODULE") === "TokenBridge", do: true, else: false
end
def format_usd_value(nil), do: ""
def format_usd_value(value) do
"#{format_currency_value(value)} USD"
if Decimal.is_decimal(value) do
"#{format_currency_value(Decimal.to_float(value))} USD"
else
"#{format_currency_value(value)} USD"
end
end
def format_currency_value(value, symbol \\ "$")
def format_currency_value(nil, _symbol), do: ""
def format_currency_value(%Decimal{} = value, symbol) do
value
|> Decimal.to_float()
|> format_currency_value(symbol)
end
defp format_currency_value(value, symbol \\ "$")
def format_currency_value(value, _symbol) when not is_float(value) do
"N/A"
end
defp format_currency_value(value, symbol) when value < 0 do
"#{symbol}0.000000"
def format_currency_value(value, symbol) when is_float(value) and value < 0 do
"#{symbol}0.00"
end
defp format_currency_value(value, symbol) when value < 0.000001 do
def format_currency_value(value, symbol) when is_float(value) and value < 0.000001 do
"Less than #{symbol}0.000001"
end
defp format_currency_value(value, _symbol) when value < 1 do
"#{number_to_currency(value, precision: 6)}"
def format_currency_value(value, symbol) when is_float(value) and value < 1 do
"#{number_to_currency(value, unit: symbol, precision: 6)}"
end
defp format_currency_value(value, _symbol) when value < 100_000 do
"#{number_to_currency(value)}"
def format_currency_value(value, symbol) when is_float(value) and value < 100_000 do
"#{number_to_currency(value, unit: symbol)}"
end
defp format_currency_value(value, _symbol) when value >= 1_000_000 and value <= 999_000_000 do
def format_currency_value(value, _symbol) when value >= 1_000_000 and value <= 999_000_000 do
{:ok, value} = Cldr.Number.to_string(value, format: :short, currency: :USD, fractional_digits: 2)
value
end
defp format_currency_value(value, _symbol) do
"#{number_to_currency(value, precision: 0)}"
def format_currency_value(value, symbol) when is_float(value) do
"#{number_to_currency(value, unit: symbol, precision: 0)}"
end
defp gas_prices do

@ -137,22 +137,6 @@ defmodule BlockScoutWeb.WebRouter do
as: :verify_contract
)
# if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do
# resources(
# "/contract_verifications",
# AddressContractVerificationController,
# only: [:new],
# as: :verify_contract
# )
# else
# resources(
# "/contract_verifications",
# AddressContractVerificationViaFlattenedCodeController,
# only: [:new],
# as: :verify_contract
# )
# end
resources(
"/verify-via-flattened-code",
AddressContractVerificationViaFlattenedCodeController,
@ -389,14 +373,14 @@ defmodule BlockScoutWeb.WebRouter do
get("/search-logs", AddressLogsController, :search_logs)
get("/search-results", SearchController, :search_results)
get("/csv-export", CsvExportController, :index)
post("/captcha", CaptchaController, :index)
get("/transactions-csv", AddressTransactionController, :transactions_csv)
get("/search-results", SearchController, :search_results)
get("/token-autocomplete", ChainController, :token_autocomplete)
get("/token-transfers-csv", AddressTransactionController, :token_transfers_csv)

@ -85,7 +85,8 @@ defmodule BlockScoutWeb.AddressControllerTest do
assert conn.status == 200
{:ok, response} = Jason.decode(conn.resp_body)
assert %{"transaction_count" => 0, "validation_count" => 0, "gas_usage_count" => 0} == response
assert %{"transaction_count" => 0, "token_transfer_count" => 0, "validation_count" => 0, "gas_usage_count" => 0} ==
response
end
end
end

@ -117,7 +117,7 @@ defmodule BlockScoutWeb.AddressTokenControllerTest do
get(conn, address_token_path(BlockScoutWeb.Endpoint, :index, Address.checksum(address.hash)), %{
"token_name" => name,
"token_type" => type,
"token_inserted_at" => inserted_at,
"value" => 1000,
"type" => "JSON"
})

@ -170,8 +170,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
insert(:token_transfer, transaction: transaction, from_address: address)
insert(:token_transfer, transaction: transaction, to_address: address)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
get(conn, "/token-transfers-csv", %{
@ -196,8 +196,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
|> insert(from_address: address)
|> with_block()
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
get(conn, "/transactions-csv", %{
@ -259,8 +259,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
transaction_index: transaction_3.index
)
from_period = Timex.format!(Timex.shift(Timex.now(), years: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), years: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
get(conn, "/internal-transactions-csv", %{
@ -316,8 +316,8 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
block_number: transaction_3.block_number
)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
conn =
get(conn, "/logs-csv", %{

@ -139,7 +139,8 @@ defmodule BlockScoutWeb.ViewingChainTest do
3,
:token_transfer,
transaction: transaction,
token_contract_address: contract_token_address
token_contract_address: contract_token_address,
block: block
)
start_supervised!(AddressesCounter)

@ -20,9 +20,9 @@ defmodule BlockScoutWeb.AddressTokenBalanceViewTest do
token_balance_a = build(:token_balance, token: build(:token, type: "ERC-20"))
token_balance_b = build(:token_balance, token: build(:token, type: "ERC-721"))
token_balances = [{token_balance_a, %{}}, {token_balance_b, %{}}]
token_balances = [{token_balance_a, %{}, %{}}, {token_balance_b, %{}, %{}}]
assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [{token_balance_a, %{}}]
assert AddressTokenBalanceView.filter_by_type(token_balances, "ERC-20") == [{token_balance_a, %{}, %{}}]
end
end
@ -116,23 +116,23 @@ defmodule BlockScoutWeb.AddressTokenBalanceViewTest do
)
token_balances = [
{token_balance_a, %{}},
{token_balance_b, %{}},
{token_balance_c, %{}},
{token_balance_d, %{}},
{token_balance_e, %{}},
{token_balance_f, %{}},
{token_balance_g, %{}}
{token_balance_a, %{}, token_balance_a.token},
{token_balance_b, %{}, token_balance_b.token},
{token_balance_c, %{}, token_balance_c.token},
{token_balance_d, %{}, token_balance_d.token},
{token_balance_e, %{}, token_balance_e.token},
{token_balance_f, %{}, token_balance_f.token},
{token_balance_g, %{}, token_balance_g.token}
]
expected = [
{token_balance_b, %{}},
{token_balance_a, %{}},
{token_balance_c, %{}},
{token_balance_d, %{}},
{token_balance_g, %{}},
{token_balance_e, %{}},
{token_balance_f, %{}}
{token_balance_b, %{}, token_balance_b.token},
{token_balance_a, %{}, token_balance_a.token},
{token_balance_c, %{}, token_balance_c.token},
{token_balance_d, %{}, token_balance_d.token},
{token_balance_g, %{}, token_balance_g.token},
{token_balance_e, %{}, token_balance_e.token},
{token_balance_f, %{}, token_balance_f.token}
]
assert AddressTokenBalanceView.sort_by_usd_value_and_name(token_balances) == expected

@ -110,52 +110,9 @@ defmodule EthereumJSONRPC.Transaction do
transaction_index: 0
}
Ganache bug: https://github.com/trufflesuite/ganache/issues/997
Invalid input of `0x0` is converted to `0x`.
iex> EthereumJSONRPC.Transaction.elixir_to_params(
...> %{
...> "blockHash" => "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd",
...> "blockNumber" => 46147,
...> "from" => "0xa1e4380a3b1f749673e270229993ee55f35663b4",
...> "gas" => 21000,
...> "gasPrice" => 50000000000000,
...> "hash" => "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060",
...> "input" => "0x0",
...> "nonce" => 0,
...> "r" => 61965845294689009770156372156374760022787886965323743865986648153755601564112,
...> "s" => 31606574786494953692291101914709926755545765281581808821704454381804773090106,
...> "to" => "0x5df9b87991262f6ba471f09758cde1c0fc1de734",
...> "transactionIndex" => 0,
...> "v" => 28,
...> "value" => 31337
...> }
...> )
%{
block_hash: "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd",
block_number: 46147,
from_address_hash: "0xa1e4380a3b1f749673e270229993ee55f35663b4",
gas: 21000,
gas_price: 50000000000000,
hash: "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060",
index: 0,
input: "0x",
nonce: 0,
r: 61965845294689009770156372156374760022787886965323743865986648153755601564112,
s: 31606574786494953692291101914709926755545765281581808821704454381804773090106,
to_address_hash: "0x5df9b87991262f6ba471f09758cde1c0fc1de734",
v: 28,
value: 31337,
transaction_index: 0
}
"""
@spec elixir_to_params(elixir) :: params
def elixir_to_params(%{"input" => "0x0"} = transaction) do
elixir_to_params(%{transaction | "input" => "0x"})
end
def elixir_to_params(
%{
"blockHash" => block_hash,
@ -293,38 +250,6 @@ defmodule EthereumJSONRPC.Transaction do
end
end
# Ganache bug. it return `to: "0x0"` except of `to: null`
def elixir_to_params(
%{
"to" => "0x0"
} = transaction
) do
%{transaction | "to" => nil}
|> elixir_to_params()
end
# Ganache bug. It don't send `r,s,v` transaction fields.
# Fix is in sources but not released yet
def elixir_to_params(
%{
"blockHash" => _,
"blockNumber" => _,
"from" => _,
"gas" => _,
"gasPrice" => _,
"hash" => _,
"input" => _,
"nonce" => _,
"to" => _,
"transactionIndex" => _,
"value" => _
} = transaction
) do
transaction
|> Map.merge(%{"r" => 0, "s" => 0, "v" => 0})
|> elixir_to_params()
end
@doc """
Extracts `t:EthereumJSONRPC.hash/0` from transaction `params`

@ -107,6 +107,10 @@ config :explorer, Explorer.Counters.AddressTransactionsCounter,
enabled: true,
enable_consolidation: true
config :explorer, Explorer.Counters.AddressTokenTransfersCounter,
enabled: true,
enable_consolidation: true
config :explorer, Explorer.Counters.BlockBurnedFeeCounter,
enabled: true,
enable_consolidation: true

@ -87,6 +87,7 @@ defmodule Explorer.Application do
configure(Explorer.Counters.AddressesWithBalanceCounter),
configure(Explorer.Counters.AddressesCounter),
configure(Explorer.Counters.AddressTransactionsCounter),
configure(Explorer.Counters.AddressTokenTransfersCounter),
configure(Explorer.Counters.AddressTransactionsGasUsageCounter),
configure(Explorer.Counters.AddressTokenUsdSum),
configure(Explorer.Counters.TokenHoldersCounter),

@ -429,9 +429,12 @@ defmodule Explorer.Chain do
end
defp address_to_transactions_tasks_query(options) do
from_block = from_block(options)
to_block = to_block(options)
options
|> Keyword.get(:paging_options, @default_paging_options)
|> fetch_transactions()
|> fetch_transactions(from_block, to_block)
end
defp transactions_block_numbers_at_address(address_hash, options) do
@ -448,9 +451,13 @@ defmodule Explorer.Chain do
direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
from_block = from_block(options)
to_block = to_block(options)
options
|> address_to_transactions_tasks_query()
|> Transaction.not_dropped_or_replaced_transacions()
|> where_block_number_in_period(from_block, to_block)
|> join_associations(necessity_by_association)
|> Transaction.matching_address_queries_list(direction, address_hash)
|> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end)
@ -2604,6 +2611,18 @@ defmodule Explorer.Chain do
end
end
@spec address_to_token_transfer_count(Address.t()) :: non_neg_integer()
def address_to_token_transfer_count(address) do
query =
from(
token_transfer in TokenTransfer,
where: token_transfer.to_address_hash == ^address.hash,
or_where: token_transfer.from_address_hash == ^address.hash
)
Repo.aggregate(query, :count, timeout: :infinity)
end
@spec address_to_gas_usage_count(Address.t()) :: non_neg_integer()
def address_to_gas_usage_count(address) do
if contract?(address) do
@ -2634,7 +2653,7 @@ defmodule Explorer.Chain do
def address_tokens_usd_sum(token_balances) do
token_balances
|> Enum.reduce(Decimal.new(0), fn {token_balance, _}, acc ->
|> Enum.reduce(Decimal.new(0), fn {token_balance, _, _}, acc ->
if token_balance.value && token_balance.token.usd_value do
Decimal.add(acc, balance_in_usd(token_balance))
else
@ -4435,9 +4454,10 @@ defmodule Explorer.Chain do
if Repo.one(query), do: true, else: false
end
defp fetch_transactions(paging_options \\ nil) do
defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil) do
Transaction
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
|> where_block_number_in_period(from_block, to_block)
|> handle_paging_options(paging_options)
end
@ -4612,6 +4632,29 @@ defmodule Explorer.Chain do
)
end
def page_token_balances(query, %PagingOptions{key: nil}), do: query
def page_token_balances(query, %PagingOptions{key: {value, address_hash}}) do
where(
query,
[tb],
tb.value < ^value or (tb.value == ^value and tb.address_hash < ^address_hash)
)
end
def page_current_token_balances(query, %PagingOptions{key: nil}), do: query
def page_current_token_balances(query, paging_options: %PagingOptions{key: nil}), do: query
def page_current_token_balances(query, paging_options: %PagingOptions{key: {name, type, value}}) do
where(
query,
[ctb, bt, t],
ctb.value < ^value or (ctb.value == ^value and t.type < ^type) or
(ctb.value == ^value and t.type == ^type and t.name < ^name)
)
end
@doc """
Ensures the following conditions are true:
@ -5819,6 +5862,14 @@ defmodule Explorer.Chain do
|> Repo.all()
end
@spec fetch_last_token_balances(Hash.Address.t(), [paging_options]) :: []
def fetch_last_token_balances(address_hash, paging_options) do
address_hash
|> CurrentTokenBalance.last_token_balances(paging_options)
|> page_current_token_balances(paging_options)
|> Repo.all()
end
@spec erc721_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) ::
{:ok, TokenTransfer.t()} | {:error, :not_found}
def erc721_token_instance_from_token_id_and_token_address(token_id, token_contract_address) do
@ -7234,40 +7285,6 @@ defmodule Explorer.Chain do
end
end
defp from_block(options) do
Keyword.get(options, :from_block) || nil
end
def to_block(options) do
Keyword.get(options, :to_block) || nil
end
def convert_date_to_min_block(date_str) do
date_format = "{YYYY}-{0M}-{0D}"
{:ok, date} =
date_str
|> Timex.parse(date_format)
{:ok, day_before} =
date
|> Timex.shift(days: -1)
|> Timex.format(date_format)
convert_date_to_max_block(day_before)
end
def convert_date_to_max_block(date) do
query =
from(block in Block,
where: fragment("DATE(timestamp) = TO_DATE(?, 'YYYY-MM-DD')", ^date),
select: max(block.number)
)
query
|> Repo.one()
end
def total_gas(gas_items) do
gas_items
|> Enum.reduce(Decimal.new(0), fn gas_item, acc ->
@ -7508,4 +7525,38 @@ defmodule Explorer.Chain do
nil
end
end
defp from_block(options) do
Keyword.get(options, :from_block) || nil
end
def to_block(options) do
Keyword.get(options, :to_block) || nil
end
def convert_date_to_min_block(date_str) do
date_format = "%Y-%m-%d"
{:ok, date} =
date_str
|> Timex.parse(date_format, :strftime)
{:ok, day_before} =
date
|> Timex.shift(days: -1)
|> Timex.format(date_format, :strftime)
convert_date_to_max_block(day_before)
end
def convert_date_to_max_block(date) do
query =
from(block in Block,
where: fragment("DATE(timestamp) = TO_DATE(?, 'YYYY-MM-DD')", ^date),
select: max(block.number)
)
query
|> Repo.one()
end
end

@ -9,7 +9,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
use Explorer.Schema
import Ecto.Changeset
import Ecto.Query, only: [from: 2, limit: 2, offset: 2, order_by: 3, preload: 2, where: 3]
import Ecto.Query, only: [from: 2, limit: 2, offset: 2, order_by: 3, preload: 2]
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, Block, BridgedToken, Hash, Token}
@ -99,7 +99,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
|> token_holders_query
|> preload(:address)
|> order_by([tb], desc: :value, desc: :address_hash)
|> page_token_balances(paging_options)
|> Chain.page_token_balances(paging_options)
|> limit(^paging_options.page_size)
|> offset(^offset)
end
@ -123,7 +123,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
|> token_holders_by_token_id_query(token_id)
|> preload(:address)
|> order_by([tb], desc: :value, desc: :address_hash)
|> page_token_balances(paging_options)
|> Chain.page_token_balances(paging_options)
|> limit(^paging_options.page_size)
|> offset(^offset)
end
@ -165,11 +165,25 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
where: ctb.value > 0,
left_join: bt in BridgedToken,
on: ctb.token_contract_address_hash == bt.home_token_contract_address_hash,
left_join: t in Token,
on: ctb.token_contract_address_hash == t.contract_address_hash,
preload: :token,
select: {ctb, bt}
select: {ctb, bt, t},
order_by: [desc: ctb.value, asc: t.type, asc: t.name]
)
end
@doc """
Builds an `t:Ecto.Query.t/0` to fetch the current token balances of the given address (paginated version).
"""
def last_token_balances(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hash
|> last_token_balances()
|> limit(^paging_options.page_size)
end
@doc """
Builds an `t:Ecto.Query.t/0` to fetch the current balance of the given address for the given token.
"""
@ -237,14 +251,4 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do
where: tb.value > 0
)
end
defp page_token_balances(query, %PagingOptions{key: nil}), do: query
defp page_token_balances(query, %PagingOptions{key: {value, address_hash}}) do
where(
query,
[tb],
tb.value < ^value or (tb.value == ^value and tb.address_hash < ^address_hash)
)
end
end

@ -4,22 +4,9 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
"""
alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{TokenTransfer, Transaction}
alias Explorer.Chain.{Address, AddressTransactionCsvExporter, TokenTransfer}
alias NimbleCSV.RFC4180
@necessity_by_association [
necessity_by_association: %{
[created_contract_address: :names] => :optional,
[from_address: :names] => :optional,
[to_address: :names] => :optional,
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
[token_transfers: :token_contract_address] => :optional,
:block => :required
}
]
@page_size 1000
@paging_options %PagingOptions{page_size: @page_size + 1}
@ -29,36 +16,12 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
to_block = Chain.convert_date_to_max_block(to_period)
address.hash
|> fetch_all_transactions(from_block, to_block, @paging_options)
|> AddressTransactionCsvExporter.fetch_all_transactions(from_block, to_block, @paging_options)
|> to_token_transfers()
|> to_csv_format(address)
|> dump_to_stream()
end
defp fetch_all_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do
options =
@necessity_by_association
|> Keyword.merge(paging_options: paging_options)
|> Keyword.put(:from_block, from_block)
|> Keyword.put(:to_block, to_block)
transactions =
address_hash
|> Chain.address_to_transactions_with_rewards(options)
|> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end)
new_acc = transactions ++ acc
case Enum.split(transactions, @page_size) do
{_transactions, [%Transaction{block_number: block_number, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, index}}
fetch_all_transactions(address_hash, from_block, to_block, new_paging_options, new_acc)
{_, []} ->
new_acc
end
end
defp to_token_transfers(transactions) do
transactions
|> Enum.flat_map(fn transaction ->

@ -43,7 +43,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
|> dump_to_stream()
end
defp fetch_all_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do
def fetch_all_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do
options =
@necessity_by_association
|> Keyword.put(:paging_options, paging_options)

@ -133,8 +133,24 @@ defmodule Explorer.Chain.Log do
with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping),
do: {:ok, identifier, text, mapping}
text <- function_call(selector.function, mapping) do
{:ok, identifier, text, mapping}
else
{:error, :could_not_decode} ->
case find_candidates(log, transaction) do
{:error, :contract_not_verified, []} ->
{:error, :could_not_decode}
{:error, :contract_not_verified, candidates} ->
{:error, :contract_verified, candidates}
_ ->
{:error, :could_not_decode}
end
output ->
output
end
_ ->
find_candidates(log, transaction)

@ -208,8 +208,8 @@ defmodule Explorer.Chain.SmartContract do
abi: [function_description],
verified_via_sourcify: boolean | nil,
partially_verified: boolean | nil,
is_vyper_contract: boolean | nil,
file_path: String.t()
file_path: String.t(),
is_vyper_contract: boolean | nil
}
schema "smart_contracts" do
@ -224,8 +224,8 @@ defmodule Explorer.Chain.SmartContract do
field(:abi, {:array, :map})
field(:verified_via_sourcify, :boolean)
field(:partially_verified, :boolean)
field(:is_vyper_contract, :boolean)
field(:file_path, :string)
field(:is_vyper_contract, :boolean)
has_many(
:decompiled_smart_contracts,
@ -262,8 +262,8 @@ defmodule Explorer.Chain.SmartContract do
:optimization_runs,
:verified_via_sourcify,
:partially_verified,
:is_vyper_contract,
:file_path
:file_path,
:is_vyper_contract
])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash)
@ -284,8 +284,8 @@ defmodule Explorer.Chain.SmartContract do
:constructor_arguments,
:verified_via_sourcify,
:partially_verified,
:is_vyper_contract,
:file_path
:file_path,
:is_vyper_contract
])
|> validate_required([:name, :compiler_version, :optimization, :address_hash])

@ -483,7 +483,27 @@ defmodule Explorer.Chain.Transaction do
to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}},
hash: hash
}) do
do_decoded_input_data(data, abi, address_hash, hash)
case do_decoded_input_data(data, abi, address_hash, hash) do
# In some cases transactions use methods of some unpredictadle contracts, so we can try to look up for method in a whole DB
{:error, :could_not_decode} ->
case decoded_input_data(%__MODULE__{
to_address: %{smart_contract: nil},
input: %{bytes: data},
hash: hash
}) do
{:error, :contract_not_verified, []} ->
{:error, :could_not_decode}
{:error, :contract_not_verified, candidates} ->
{:error, :contract_verified, candidates}
_ ->
{:error, :could_not_decode}
end
output ->
output
end
end
defp do_decoded_input_data(data, abi, address_hash, hash) do

@ -0,0 +1,119 @@
defmodule Explorer.Counters.AddressTokenTransfersCounter do
@moduledoc """
Caches Address token transfers counter.
"""
use GenServer
alias Explorer.Chain
@cache_name :address_token_transfers_counter
@last_update_key "last_update"
@ets_opts [
:set,
:named_table,
:public,
read_concurrency: true
]
config = Application.get_env(:explorer, Explorer.Counters.AddressTokenTransfersCounter)
@enable_consolidation Keyword.get(config, :enable_consolidation)
@spec start_link(term()) :: GenServer.on_start()
def start_link(_) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
def init(_args) do
create_cache_table()
{:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}}
end
@impl true
def handle_continue(:ok, %{consolidate?: true} = state) do
{:noreply, state}
end
@impl true
def handle_continue(:ok, state) do
{:noreply, state}
end
@impl true
def handle_info(:consolidate, state) do
{:noreply, state}
end
def fetch(address) do
if cache_expired?(address) do
Task.start_link(fn ->
update_cache(address)
end)
end
address_hash_string = get_address_hash_string(address)
fetch_from_cache("hash_#{address_hash_string}")
end
def cache_name, do: @cache_name
defp cache_expired?(address) do
cache_period = address_token_transfers_counter_cache_period()
address_hash_string = get_address_hash_string(address)
updated_at = fetch_from_cache("hash_#{address_hash_string}_#{@last_update_key}")
cond do
is_nil(updated_at) -> true
current_time() - updated_at > cache_period -> true
true -> false
end
end
defp update_cache(address) do
address_hash_string = get_address_hash_string(address)
put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", current_time())
new_data = Chain.address_to_token_transfer_count(address)
put_into_cache("hash_#{address_hash_string}", new_data)
end
defp fetch_from_cache(key) do
case :ets.lookup(@cache_name, key) do
[{_, value}] ->
value
[] ->
0
end
end
defp put_into_cache(key, value) do
:ets.insert(@cache_name, {key, value})
end
defp get_address_hash_string(address) do
Base.encode16(address.hash.bytes, case: :lower)
end
defp current_time do
utc_now = DateTime.utc_now()
DateTime.to_unix(utc_now, :millisecond)
end
def create_cache_table do
if :ets.whereis(@cache_name) == :undefined do
:ets.new(@cache_name, @ets_opts)
end
end
def enable_consolidation?, do: @enable_consolidation
defp address_token_transfers_counter_cache_period do
case Integer.parse(System.get_env("ADDRESS_TOKEN_TRANSFERS_COUNTER_CACHE_PERIOD", "")) do
{secs, ""} -> :timer.seconds(secs)
_ -> :timer.hours(1)
end
end
end

@ -84,8 +84,8 @@ defmodule Explorer.Market do
def add_price(tokens) when is_list(tokens) do
Enum.map(tokens, fn item ->
case item do
{token_balance, bridged_token} ->
{add_price(token_balance), bridged_token}
{token_balance, bridged_token, token} ->
{add_price(token_balance), bridged_token, token}
token_balance ->
add_price(token_balance)

@ -112,7 +112,7 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
init_without_0x
_ ->
nil
bytecode
end
%{
@ -177,6 +177,10 @@ defmodule Explorer.SmartContract.Solidity.Verifier do
For more information on the swarm hash, check out:
https://solidity.readthedocs.io/en/v0.5.3/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
"""
def extract_bytecode_and_metadata_hash(nil) do
%{"metadata_hash" => nil, "bytecode" => nil, "compiler_version" => nil}
end
def extract_bytecode_and_metadata_hash("0x" <> code) do
%{"metadata_hash" => metadata_hash, "bytecode" => bytecode, "compiler_version" => compiler_version} =
extract_bytecode_and_metadata_hash(code)

@ -220,6 +220,7 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
)
<<>> ->
# we should consdider change: use just :false
check_func.("")
<<_::binary-size(2)>> <> rest ->
@ -278,10 +279,46 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
if check_func_result do
check_func_result
else
extract_constructor_arguments(filtered_constructor_arguments, check_func, contract_source_code, contract_name)
output =
extract_constructor_arguments(filtered_constructor_arguments, check_func, contract_source_code, contract_name)
if output do
output
else
# https://github.com/blockscout/blockscout/pull/4764
clean_constructor_arguments =
remove_substring_of_require_messages(filtered_constructor_arguments, contract_source_code, contract_name)
check_func.(clean_constructor_arguments)
end
end
end
defp remove_substring_of_require_messages(constructor_arguments, contract_source_code, contract_name) do
require_msgs =
contract_source_code
|> extract_require_messages_from_constructor(contract_name)
|> Enum.filter(fn require_msg -> require_msg != nil end)
longest_substring_to_delete =
Enum.reduce(require_msgs, "", fn msg, best_match ->
case String.myers_difference(constructor_arguments, msg) do
[{:eq, match} | _] ->
if String.length(match) > String.length(best_match) do
match
else
best_match
end
_ ->
best_match
end
end)
[_, cleaned_constructor_arguments] = String.split(constructor_arguments, longest_substring_to_delete, parts: 2)
cleaned_constructor_arguments
end
def remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name) do
all_msgs =
contract_source_code
@ -345,7 +382,8 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
assumed_arguments
rescue
_ -> false
_ ->
false
end
end

@ -113,8 +113,6 @@ defmodule Explorer.SmartContract.VyperDownloader do
Enum.reduce_while(release_assets, "", fn asset, acc ->
browser_download_url = Map.get(asset, "browser_download_url")
# darwin is for local tests
# if browser_download_url =~ "darwin" do
if browser_download_url =~ "linux" do
{:halt, browser_download_url}
else

@ -157,7 +157,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalanceTest do
address.hash
|> CurrentTokenBalance.last_token_balances()
|> Repo.all()
|> Enum.map(fn {token_balance, _} -> token_balance.address_hash end)
|> Enum.map(fn {token_balance, _, _} -> token_balance.address_hash end)
assert token_balances == [current_token_balance.address_hash]
end
@ -195,7 +195,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalanceTest do
address.hash
|> CurrentTokenBalance.last_token_balances()
|> Repo.all()
|> Enum.map(fn {token_balance, _} -> token_balance.address_hash end)
|> Enum.map(fn {token_balance, _, _} -> token_balance.address_hash end)
assert token_balances == [current_token_balance_a.address_hash]
end

@ -23,8 +23,8 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do
transaction_index: transaction.index
)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
address
@ -133,8 +133,8 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do
end)
|> Enum.count()
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
address

@ -1,7 +1,7 @@
defmodule Explorer.Chain.AddressLogCsvExporterTest do
use Explorer.DataCase
alias Explorer.Chain.{AddressLogCsvExporter, Wei}
alias Explorer.Chain.AddressLogCsvExporter
describe "export/3" do
test "exports address logs to csv" do
@ -26,8 +26,8 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do
fourth_topic: "0x16"
)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
address
@ -74,7 +74,7 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do
assert result.index == to_string(log.index)
assert result.block_number == to_string(log.block_number)
assert result.block_hash == to_string(log.block_hash)
assert result.address == to_string(log.address)
assert result.address == String.downcase(to_string(log.address))
assert result.data == to_string(log.data)
assert result.first_topic == to_string(log.first_topic)
assert result.second_topic == to_string(log.second_topic)
@ -102,8 +102,8 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do
end)
|> Enum.count()
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
address

@ -14,8 +14,8 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do
token_transfer = insert(:token_transfer, transaction: transaction, from_address: address)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
address

@ -13,8 +13,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
|> with_block()
|> Repo.preload(:token_transfers)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
[result] =
address
@ -96,8 +96,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do
end)
|> Enum.count()
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d %H:%M", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d %H:%M", :strftime)
from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime)
to_period = Timex.format!(Timex.now(), "%Y-%m-%d", :strftime)
result =
address

@ -4881,7 +4881,7 @@ defmodule Explorer.ChainTest do
token_balances =
address.hash
|> Chain.fetch_last_token_balances()
|> Enum.map(fn {token_balance, _} -> token_balance.address_hash end)
|> Enum.map(fn {token_balance, _, _} -> token_balance.address_hash end)
assert token_balances == [current_token_balance.address_hash]
end

@ -35,7 +35,7 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do
when not is_nil(address_hash) do
stale_current_token_balances =
current_token_balances
|> Enum.filter(fn {current_token_balance, _} -> current_token_balance.block_number < stale_balance_window end)
|> Enum.filter(fn {current_token_balance, _, _} -> current_token_balance.block_number < stale_balance_window end)
if Enum.count(stale_current_token_balances) > 0 do
fetch_and_update(latest_block_number, address_hash, stale_current_token_balances)
@ -49,11 +49,10 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do
defp fetch_and_update(block_number, address_hash, stale_current_token_balances) do
current_token_balances_update_params =
stale_current_token_balances
|> Enum.map(fn {stale_current_token_balance, _} ->
|> Enum.map(fn {stale_current_token_balance, _, token} ->
stale_current_token_balances_to_fetch = [
%{
token_contract_address_hash:
"0x" <> Base.encode16(stale_current_token_balance.token_contract_address_hash.bytes),
token_contract_address_hash: "0x" <> Base.encode16(token.contract_address_hash.bytes),
address_hash: "0x" <> Base.encode16(address_hash.bytes),
block_number: block_number
}
@ -65,8 +64,8 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do
if updated_balance do
%{}
|> Map.put(:address_hash, stale_current_token_balance.address_hash)
|> Map.put(:token_contract_address_hash, stale_current_token_balance.token_contract_address_hash)
|> Map.put(:token_type, stale_current_token_balance.token.type)
|> Map.put(:token_contract_address_hash, token.contract_address_hash)
|> Map.put(:token_type, token.type)
|> Map.put(:block_number, block_number)
|> Map.put(:value, Decimal.new(updated_balance))
|> Map.put(:value_fetched_at, DateTime.utc_now())

Loading…
Cancel
Save