diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 200bce21f743..76d535c23168 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -5,11 +5,12 @@ :0: Unknown type 'Elixir.Address':t/0 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return lib/explorer/repo/prometheus_logger.ex:8 -lib/explorer/smart_contract/publisher_worker.ex:6 +lib/explorer/smart_contract/solidity/publisher_worker.ex:1 +lib/explorer/smart_contract/vyper/publisher_worker.ex:1 +lib/explorer/smart_contract/solidity/publisher_worker.ex:6 +lib/explorer/smart_contract/vyper/publisher_worker.ex:6 apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() -apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true' -apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true' lib/block_scout_web/router.ex:1 lib/block_scout_web/schema/types.ex:31 lib/phoenix/router.ex:324 @@ -18,11 +19,14 @@ lib/block_scout_web/views/layout_view.ex:145: The call 'Elixir.Poison.Parser':'p lib/block_scout_web/views/layout_view.ex:237: The call 'Elixir.Poison.Parser':'parse!' lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21 lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22 -lib/explorer/smart_contract/reader.ex:344 +lib/explorer/smart_contract/reader.ex:440 lib/indexer/fetcher/token_total_supply_on_demand.ex:16 lib/explorer/exchange_rates/source.ex:110 lib/explorer/exchange_rates/source.ex:113 lib/explorer/smart_contract/verifier.ex:89 +lib/explorer/smart_contract/solidity/verifier.ex:89 +lib/block_scout_web/templates/address_contract/index.html.eex:156 +lib/block_scout_web/templates/address_contract/index.html.eex:199 lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/7 has no local return lib/explorer/staking/stake_snapshotting.ex:147 lib/explorer/third_party_integrations/sourcify.ex:70 @@ -33,4 +37,8 @@ lib/block_scout_web/views/transaction_view.ex:197 lib/block_scout_web/templates/address_contract/index.html.eex:168 lib/block_scout_web/templates/address_contract/index.html.eex:211 lib/explorer/smart_contract/reader.ex:379 +lib/explorer/smart_contract/reader.ex:478 +lib/indexer/buffered_task.ex:402 +lib/indexer/buffered_task.ex:451 +lib/indexer/memory/monitor.ex:161 diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 5c8845a57e96..21d6fde2cd12 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -11,8 +11,8 @@ on: env: MIX_ENV: test - OTP_VERSION: '23.3.4.1' - ELIXIR_VERSION: '1.11.4' + OTP_VERSION: '24.1' + ELIXIR_VERSION: '1.12.3' jobs: build-and-cache: @@ -35,10 +35,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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- @@ -90,15 +90,15 @@ jobs: with: otp-version: ${{ env.OTP_VERSION }} elixir-version: ${{ env.ELIXIR_VERSION }} - + - name: Restore Mix Deps Cache uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -119,10 +119,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -142,10 +142,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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_1-${{ 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-" @@ -182,16 +182,16 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" - run: | mix gettext.extract --merge | tee stdout.txt - ! grep "Wrote " stdout.txt + cat stdout.txt | grep '(0 new translations' | grep ' 0 removed' | grep ' 0 reworded' working-directory: "apps/block_scout_web" sobelow: name: Sobelow security analysis @@ -208,10 +208,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -236,10 +236,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -285,10 +285,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -347,10 +347,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -410,10 +410,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -484,10 +484,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -552,10 +552,10 @@ jobs: uses: actions/cache@v2 id: deps-cache with: - path: | + path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_1-${{ 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-" @@ -627,4 +627,4 @@ jobs: - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1 with: - files: artifacts/**/*.xml \ No newline at end of file + files: artifacts/**/*.xml diff --git a/.tool-versions b/.tool-versions index 0ea653fbabc0..1dad75002602 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.11.4-otp-23 -erlang 23.3.4.1 -nodejs 14.17.0 +elixir 1.12.3 +erlang 24.1 +nodejs 14.18.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index e4de0c5da540..d6868dceff83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ ## 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 +- [#4656](https://github.com/blockscout/blockscout/pull/4656) - Open in Tenderly button +- [#4655](https://github.com/blockscout/blockscout/pull/4655), [#4676](https://github.com/blockscout/blockscout/pull/4676) - EIP-3091 support +- [#4621](https://github.com/blockscout/blockscout/pull/4621) - Add beacon contract address slot for proxy - [#4625](https://github.com/blockscout/blockscout/pull/4625) - Contract address page: Add implementation link to the overview of proxy contracts - [#4624](https://github.com/blockscout/blockscout/pull/4624) - Support HTML tags in alert message - [#4608](https://github.com/blockscout/blockscout/pull/4608), [#4622](https://github.com/blockscout/blockscout/pull/4622) - Block Details page: Improved style of transactions button @@ -10,7 +20,20 @@ - [#4579](https://github.com/blockscout/blockscout/pull/4579) - Write contract page: Resize inputs; Improve multiplier selector ### Fixes +- [#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 - [#4713](https://github.com/blockscout/blockscout/pull/4713) - Search input field: sanitize input +- [#4703](https://github.com/blockscout/blockscout/pull/4703) - Block Details page: Fix pagination on the Transactions tab +- [#4686](https://github.com/blockscout/blockscout/pull/4686) - Block page: check gas limit value before division +- [#4678](https://github.com/blockscout/blockscout/pull/4678) - Internal transactions indexer: fix issue of some pending transactions never become confirmed +- [#4668](https://github.com/blockscout/blockscout/pull/4668) - Fix css for dark theme +- [#4654](https://github.com/blockscout/blockscout/pull/4654) - AddressView: Change `@burn_address` to string `0x0000000000000000000000000000000000000000` +- [#4626](https://github.com/blockscout/blockscout/pull/4626) - Refine view of popup for reverted tx +- [#4640](https://github.com/blockscout/blockscout/pull/4640) - Token page: fixes in mobile view - [#4612](https://github.com/blockscout/blockscout/pull/4612) - Hide error selector in the contract's functions list - [#4615](https://github.com/blockscout/blockscout/pull/4615) - Fix broken style for `View more transfers` button - [#4592](https://github.com/blockscout/blockscout/pull/4592) - Add `type` field for `receive` and `fallback` entities of a Smart Contract @@ -21,8 +44,16 @@ - [#4587](https://github.com/blockscout/blockscout/pull/4587) - Enable navbar menu on Search results page - [#4582](https://github.com/blockscout/blockscout/pull/4582) - Fix NaN input on write contract page - ### 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 +- [#4682](https://github.com/blockscout/blockscout/pull/4682) - Update all possible outdated mix dependencies +- [#4663](https://github.com/blockscout/blockscout/pull/4663) - Migrate to Elixir 1.12.x +- [#4661](https://github.com/blockscout/blockscout/pull/4661) - Update NPM packages to resolve vulnerabilities +- [#4649](https://github.com/blockscout/blockscout/pull/4649) - 1559 Transaction Page: Convert Burnt Fee to ether and add price in USD +- [#4646](https://github.com/blockscout/blockscout/pull/4646) - Transaction page: Rename burned to burnt - [#4611](https://github.com/blockscout/blockscout/pull/4611) - Ability to hide miner in block views diff --git a/apps/block_scout_web/assets/__tests__/lib/currency.js b/apps/block_scout_web/assets/__tests__/lib/currency.js index e729c08f381d..407058f36220 100644 --- a/apps/block_scout_web/assets/__tests__/lib/currency.js +++ b/apps/block_scout_web/assets/__tests__/lib/currency.js @@ -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.123 USD') expect(formatUsdValue(0.12)).toEqual('$0.120 USD') diff --git a/apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js new file mode 100644 index 000000000000..73b51b1f4655 --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/lib/smart_contract/common_helpers.js @@ -0,0 +1,180 @@ +import { prepareMethodArgs } from '../../../js/lib/smart_contract/common_helpers' +import $ from 'jquery' + +const oneFieldHTML = + '
' + + ' ' + + ' ' + + '
' + + ' ' + + '
' + + ' ' + + '
' + +const twoFieldHTML = + '
' + + ' ' + + ' ' + + '
' + + ' ' + + '
' + + '
' + + ' ' + + '
' + + ' ' + + '
' + +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) +}) \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/_mixins.scss b/apps/block_scout_web/assets/css/_mixins.scss index 911b1b658718..ab5b87fa1176 100644 --- a/apps/block_scout_web/assets/css/_mixins.scss +++ b/apps/block_scout_web/assets/css/_mixins.scss @@ -135,7 +135,7 @@ color: $text-color; cursor: pointer; display: flex; - font-size: 12px; + font-size: $font-size; font-weight: 600; height: 36px; justify-content: center; diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index eccf6f678197..729bee00afd8 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -101,6 +101,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/form"; @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"; diff --git a/apps/block_scout_web/assets/css/components/_address-overview.scss b/apps/block_scout_web/assets/css/components/_address-overview.scss index f758e4affcde..6462722f2fa1 100644 --- a/apps/block_scout_web/assets/css/components/_address-overview.scss +++ b/apps/block_scout_web/assets/css/components/_address-overview.scss @@ -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; diff --git a/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss b/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss index d19b662486f0..bf4851c82bcf 100644 --- a/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss +++ b/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss @@ -4,7 +4,7 @@ $btn-dropdown-line-color-hover: #f5f6fa !default; $btn-dropdown-line-font: #333; .btn-dropdown-line { - @include btn-line($btn-dropdown-line-bg, $btn-dropdown-line-color); + @include btn-line($btn-dropdown-line-bg, $btn-dropdown-line-color, 14px); border-color: $btn-dropdown-line-color; color: $btn-dropdown-line-font; outline: none !important; diff --git a/apps/block_scout_web/assets/css/components/_btn_no_border.scss b/apps/block_scout_web/assets/css/components/_btn_no_border.scss index a958eb72149f..a74bb4530abe 100644 --- a/apps/block_scout_web/assets/css/components/_btn_no_border.scss +++ b/apps/block_scout_web/assets/css/components/_btn_no_border.scss @@ -11,9 +11,9 @@ $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: $btn-no-border-bg; + border-color: #00000000 !important; align-items: center; border-radius: 2px; display: flex; diff --git a/apps/block_scout_web/assets/css/components/_btn_wallet.scss b/apps/block_scout_web/assets/css/components/_btn_wallet.scss new file mode 100644 index 000000000000..e0d7c434a5d7 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_btn_wallet.scss @@ -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; + } +} diff --git a/apps/block_scout_web/assets/css/components/_button.scss b/apps/block_scout_web/assets/css/components/_button.scss index d743e87b204a..b370f74f919c 100644 --- a/apps/block_scout_web/assets/css/components/_button.scss +++ b/apps/block_scout_web/assets/css/components/_button.scss @@ -95,3 +95,10 @@ $button-secondary-color: $secondary !default; .button-block + .button-block { margin-top: 5px; } + +.spinner { + background-image: url('/images/spinner.svg'); + background-position: right; + background-repeat: no-repeat; + background-size: 30px; +} diff --git a/apps/block_scout_web/assets/css/components/_dropdown.scss b/apps/block_scout_web/assets/css/components/_dropdown.scss index 3fe3831dccd2..3a789d9edb09 100644 --- a/apps/block_scout_web/assets/css/components/_dropdown.scss +++ b/apps/block_scout_web/assets/css/components/_dropdown.scss @@ -101,4 +101,12 @@ $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; } diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index 965efe034740..caa5a060bf54 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -597,11 +597,6 @@ $cube-quantity: 5; @include media-breakpoint-down(sm) { display: inline-block; word-break: break-all; - } -} - -.bridged-tokens-buttons-mobile { - @include media-breakpoint-down(sm) { - margin-top: 0.5em; + line-height: 20px; } } diff --git a/apps/block_scout_web/assets/css/components/_transaction.scss b/apps/block_scout_web/assets/css/components/_transaction.scss index 4e680d653d3d..2d61de0e4d05 100644 --- a/apps/block_scout_web/assets/css/components/_transaction.scss +++ b/apps/block_scout_web/assets/css/components/_transaction.scss @@ -8,16 +8,6 @@ } } -.transaction-bottom-panel { - display: flex; - flex-direction: column; - @media (min-width: 768px) { - flex-direction: row; - justify-content: space-between; - align-items: flex-end; - } -} - .download-all-transactions { text-align: center; color: #a3a9b5; @@ -70,7 +60,7 @@ } } -.transaction-details { +.fs-14 { font-size: 14px; } .transaction-gas-used { diff --git a/apps/block_scout_web/assets/css/components/datepicker.scss b/apps/block_scout_web/assets/css/components/datepicker.scss new file mode 100644 index 000000000000..2a8a559b045b --- /dev/null +++ b/apps/block_scout_web/assets/css/components/datepicker.scss @@ -0,0 +1 @@ +@import '~pikaday/scss/pikaday'; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/export-csv.scss b/apps/block_scout_web/assets/css/export-csv.scss new file mode 100644 index 000000000000..045739115a77 --- /dev/null +++ b/apps/block_scout_web/assets/css/export-csv.scss @@ -0,0 +1,12 @@ +@import "components/datepicker"; + +.js-datepicker { + width: 150px !important; + display: inline-block !important; + margin-left: 10px; + margin-right: 10px; +} + +.dates-container { + margin-bottom: 20px; +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_dark-theme.scss b/apps/block_scout_web/assets/css/theme/_dark-theme.scss index 42c46b014a60..109e7db86660 100644 --- a/apps/block_scout_web/assets/css/theme/_dark-theme.scss +++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss @@ -1158,5 +1158,14 @@ $dark-stakes-banned-background: #3e314c; } } .dark-theme-applied .confirmations-label:after { - background: .7rem solid $dark-light-bg; + border-left: .9rem solid $dark-light-bg; +} + +.dark-theme-applied .i-tooltip-2:hover{ + color: $labels-dark; +} + +.dark-theme-applied .btn-no-border-link-to-tems { + border-color: $dark-primary !important; + color: $dark-primary !important; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/lib/ad.js b/apps/block_scout_web/assets/js/lib/ad.js index a0ac1a8ec41a..431808552fb1 100644 --- a/apps/block_scout_web/assets/js/lib/ad.js +++ b/apps/block_scout_web/assets/js/lib/ad.js @@ -9,13 +9,15 @@ function countImpressions (impressionUrl) { function showAd () { const domainName = window.location.hostname - if (domainName === 'blockscout.com') { + 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 } } diff --git a/apps/block_scout_web/assets/js/lib/currency.js b/apps/block_scout_web/assets/js/lib/currency.js index df2ffbf4d6a9..281f0d560d37 100644 --- a/apps/block_scout_web/assets/js/lib/currency.js +++ b/apps/block_scout_web/assets/js/lib/currency.js @@ -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` + } } export function formatCGValue (value) { @@ -16,8 +21,8 @@ function formatTokenUsdValue (value) { 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 `${window.localized['Less than']} ${symbol}0.000001` if (value < 10) return `${symbol}${numeral(value).format('0.000')}` if (value < 100000) return `${symbol}${numeral(value).format('0,0.00')}` @@ -56,7 +61,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) { diff --git a/apps/block_scout_web/assets/js/lib/datepicker.js b/apps/block_scout_web/assets/js/lib/datepicker.js new file mode 100644 index 000000000000..4816cdb181b3 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/datepicker.js @@ -0,0 +1,86 @@ +import * as Pikaday from 'pikaday' +import moment from 'moment' +import $ from 'jquery' + +const DATE_FORMAT = 'YYYY-MM-DD' + +const $button = $('#export-csv-button') + +// eslint-disable-next-line +const _instance1 = new Pikaday({ + field: $('.js-datepicker-from')[0], + onSelect: (date) => onSelect(date, 'from_period'), + defaultDate: moment().add(-1, 'months').toDate(), + setDefaultDate: true, + maxDate: new Date(), + format: DATE_FORMAT +}) + +// eslint-disable-next-line +const _instance2 = new Pikaday({ + field: $('.js-datepicker-to')[0], + onSelect: (date) => onSelect(date, 'to_period'), + defaultDate: new Date(), + setDefaultDate: true, + maxDate: new Date(), + format: DATE_FORMAT +}) + +$button.on('click', () => { + $button.addClass('spinner') + // eslint-disable-next-line + const resp = grecaptcha.getResponse() + if (resp) { + $.ajax({ + url: './captcha?type=JSON', + type: 'POST', + headers: { + 'x-csrf-token': $('[name=_csrf_token]').val() + }, + data: { + type: 'JSON', + captchaResponse: resp + } + }) + .done(function (data) { + // eslint-disable-next-line + grecaptcha.reset() + const dataJson = JSON.parse(data) + if (dataJson.success) { + $button.removeClass('spinner') + location.href = $button.data('link') + } else { + $button.removeClass('spinner') + return false + } + }) + .fail(function (_jqXHR, textStatus) { + $button.removeClass('spinner') + }) + } else { + $button.removeClass('spinner') + } +}) + +function onSelect (date, paramToReplace) { + const formattedDate = moment(date).format(DATE_FORMAT) + + if (date) { + var csvExportPath = $button.data('link') + + var updatedCsvExportUrl = replaceUrlParam(csvExportPath, paramToReplace, formattedDate) + $button.data('link', updatedCsvExportUrl) + } +} + +function replaceUrlParam (url, paramName, paramValue) { + if (paramValue == null) { + paramValue = '' + } + var pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)') + if (url.search(pattern) >= 0) { + return url.replace(pattern, '$1' + paramValue + '$2') + } + url = url.replace(/[?#]$/, '') + return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue +} diff --git a/apps/block_scout_web/assets/js/lib/from_now.js b/apps/block_scout_web/assets/js/lib/from_now.js index 1c95f350750e..32dc90b0fc1e 100644 --- a/apps/block_scout_web/assets/js/lib/from_now.js +++ b/apps/block_scout_web/assets/js/lib/from_now.js @@ -22,7 +22,7 @@ function updateAge (el, timestamp) { let fromNow = timestamp.fromNow() // show the exact time only for transaction details page. Otherwise, short entry const elInTile = el.hasAttribute('in-tile') - if ((window.location.pathname.includes('/tx/') || window.location.pathname.includes('/blocks/')) && !elInTile) { + if ((window.location.pathname.includes('/tx/') || window.location.pathname.includes('/block/') || window.location.pathname.includes('/blocks/')) && !elInTile) { const offset = moment().utcOffset() / 60 const sign = offset && Math.sign(offset) ? '+' : '-' const formatDate = `MMMM-DD-YYYY hh:mm:ss A ${sign}${offset} UTC` diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js index 5a03a3afa4ac..4ed34840be4d 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/common_helpers.js @@ -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) @@ -69,6 +70,18 @@ export const formatError = (error) => { return message } +export const formatTitleAndError = (error) => { + let { message } = error + var title = message && message.split('Error: ').length > 1 ? message.split('Error: ')[1] : message + title = title && title.split('{').length > 1 ? title.split('{')[0].replace(':', '') : title + try { + message = message && message.indexOf('{') >= 0 ? JSON.parse(message && message.slice(message.indexOf('{'))).error : '' + } catch (exception) { + message = '' + } + return { title: title, message: message } +} + export const getCurrentAccount = () => { return new Promise((resolve, reject) => { window.ethereum.request({ method: 'eth_accounts' }) @@ -129,7 +142,7 @@ function replaceSpaces (value, type, components) { }) .join(',') } else { - return value + return value.trim() } } diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js index 55312c1429d2..e6e444c2b02b 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/functions.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/functions.js @@ -1,6 +1,6 @@ import $ from 'jquery' import { getContractABI, getMethodInputs, prepareMethodArgs } from './common_helpers' -import { walletEnabled, connectToWallet, shouldHideConnectButton, callMethod } from './write' +import { walletEnabled, connectToWallet, shouldHideConnectButton, callMethod, queryMethod } from './write' import '../../pages/address' const loadFunctions = (element) => { @@ -120,14 +120,10 @@ const readWriteFunction = (element) => { const inputs = getMethodInputs(contractAbi, functionName) const $methodId = $form.find('input[name=method_id]') const args = prepareMethodArgs($functionInputs, inputs) + const type = $('[data-smart-contract-functions]').data('type') - const data = { - function_name: functionName, - method_id: $methodId.val(), - args - } - - $.get(url, data, response => $responseContainer.html(response)) + walletEnabled() + .then((isWalletEnabled) => queryMethod(isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer)) } else if (action === 'write') { const explorerChainId = $form.data('chainId') walletEnabled() diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/write.js b/apps/block_scout_web/assets/js/lib/smart_contract/write.js index e7bb4716018f..a9be3c5bce09 100644 --- a/apps/block_scout_web/assets/js/lib/smart_contract/write.js +++ b/apps/block_scout_web/assets/js/lib/smart_contract/write.js @@ -1,6 +1,7 @@ import Web3 from 'web3' +import $ from 'jquery' import { openErrorModal, openWarningModal, openSuccessModal, openModalWithMessage } from '../modals' -import { compareChainIDs, formatError, getContractABI, getCurrentAccount, getMethodInputs, prepareMethodArgs } from './common_helpers' +import { compareChainIDs, formatError, formatTitleAndError, getContractABI, getCurrentAccount, getMethodInputs, prepareMethodArgs } from './common_helpers' export const walletEnabled = () => { return new Promise((resolve) => { @@ -100,7 +101,8 @@ export function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $ const methodToCall = TargetContract.methods[functionName](...args).send(sendParams) methodToCall .on('error', function (error) { - openErrorModal(`Error in sending transaction for method "${functionName}"`, formatError(error), false) + var titleAndError = formatTitleAndError(error) + openErrorModal(titleAndError.title.length ? titleAndError.title : `Error in sending transaction for method "${functionName}"`, titleAndError.message, false) }) .on('transactionHash', function (txHash) { onTransactionHash(txHash, $element, functionName) @@ -128,6 +130,31 @@ export function callMethod (isWalletEnabled, $functionInputs, explorerChainId, $ }) } +export function queryMethod (isWalletEnabled, url, $methodId, args, type, functionName, $responseContainer) { + var data = { + function_name: functionName, + method_id: $methodId.val(), + type: type, + args + } + if (isWalletEnabled) { + getCurrentAccount() + .then((currentAccount) => { + data = { + function_name: functionName, + method_id: $methodId.val(), + type: type, + from: currentAccount, + args + } + $.get(url, data, response => $responseContainer.html(response)) + } + ) + } else { + $.get(url, data, response => $responseContainer.html(response)) + } +} + function onTransactionHash (txHash, $element, functionName) { openModalWithMessage($element.find('#pending-contract-write'), true, txHash) const getTxReceipt = (txHash) => { diff --git a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js index 5f07156d6d46..1a64f549db2d 100644 --- a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js +++ b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js @@ -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() diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 577b6a15f471..f60fefd631d5 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -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() diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index c848c4934bf0..3d38e60daf8b 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -123,3 +123,34 @@ if ($transactionDetailsPage.length) { }) }) } + +$(function () { + const $collapseButton = $('[button-collapse-input]') + const $expandButton = $('[button-expand-input]') + + $collapseButton.on('click', event => { + const $button = event.target + const $parent = $button.parentElement + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $collapseButton.classList.add('d-none') + $expandButton.classList.remove('d-none') + $hiddenText.classList.add('d-none') + $placeHolder.classList.remove('d-none') + }) + + $expandButton.on('click', event => { + const $button = event.target + const $parent = $button.parentElement + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $expandButton.classList.add('d-none') + $collapseButton.classList.remove('d-none') + $hiddenText.classList.remove('d-none') + $placeHolder.classList.add('d-none') + }) +}) diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js index 752ef9e07373..dc4135e05fcc 100644 --- a/apps/block_scout_web/assets/js/pages/verification_form.js +++ b/apps/block_scout_web/assets/js/pages/verification_form.js @@ -233,6 +233,7 @@ if ($contractVerificationPage.length) { if ($(this).prop('checked')) { $('#verify_via_flattened_code_button').show() $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').hide() } }) @@ -240,6 +241,15 @@ if ($contractVerificationPage.length) { if ($(this).prop('checked')) { $('#verify_via_flattened_code_button').hide() $('#verify_via_sourcify_button').show() + $('#verify_vyper_contract_button').hide() + } + }) + + $('.verify-vyper-contract').on('click', function () { + if ($(this).prop('checked')) { + $('#verify_via_flattened_code_button').hide() + $('#verify_via_sourcify_button').hide() + $('#verify_vyper_contract_button').show() } }) } diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 801f20ecb76b..e62188d33644 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -1269,9 +1269,9 @@ } }, "@ethersproject/bignumber": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.1.tgz", - "integrity": "sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.2.tgz", + "integrity": "sha512-oIBDhsKy5bs7j36JlaTzFgNPaZjiNDOXsdSgSpXRucUl+UA6L/1YLlFeI3cPAoodcenzF4nxNPV13pcy7XbWjA==", "requires": { "@ethersproject/bytes": "^5.4.0", "@ethersproject/logger": "^5.4.0", @@ -1616,15 +1616,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -2149,6 +2140,12 @@ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "@types/node": { "version": "13.13.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.5.tgz", @@ -2453,14 +2450,6 @@ "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" - }, - "dependencies": { - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - } } }, "ajv": { @@ -2660,6 +2649,12 @@ "es-abstract": "^1.17.0-next.1" } }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -3428,21 +3423,14 @@ "dev": true }, "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - } + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" } }, "caniuse-api": { @@ -3934,15 +3922,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -4134,9 +4113,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -4376,15 +4355,6 @@ } } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -4428,6 +4398,24 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -6182,6 +6170,12 @@ "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6477,9 +6471,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -6557,13 +6551,10 @@ "dev": true }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, "infer-owner": { "version": "1.0.4", @@ -6842,6 +6833,15 @@ } } }, + "is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -6905,12 +6905,6 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -7160,12 +7154,6 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8919,16 +8907,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -8989,9 +8967,9 @@ "dev": true }, "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "dev": true }, "map-visit": { @@ -9035,101 +9013,190 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" }, "dependencies": { "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "lru-cache": "^6.0.0" } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "p-locate": "^4.1.0" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" } }, - "pify": { + "p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "lru-cache": "^6.0.0" } + }, + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, @@ -9211,6 +9278,12 @@ "dom-walk": "^0.1.0" } }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "mini-css-extract-plugin": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz", @@ -9270,6 +9343,17 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, "minipass": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", @@ -9529,15 +9613,6 @@ "which": "^2.0.2" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", @@ -9602,9 +9677,9 @@ "dev": true }, "node-sass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-5.0.0.tgz", - "integrity": "sha512-opNgmlu83ZCF792U281Ry7tak9IbVC+AKnXGovcQ8LG8wFaJv6cLnRlc6DIHlmNxWEexB5bZxi9SZ9JyUuOYjw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-6.0.1.tgz", + "integrity": "sha512-f+Rbqt92Ful9gX0cGtdYwjTrWAaGURgaK5rZCWOgCNyGWusFYHhbqCCBoFBeat+HKETOU02AyTxNhJV0YZf2jQ==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -9614,8 +9689,7 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "lodash": "^4.17.15", - "meow": "^3.7.0", - "mkdirp": "^0.5.1", + "meow": "^9.0.0", "nan": "^2.13.2", "node-gyp": "^7.1.0", "npmlog": "^4.0.0", @@ -10095,9 +10169,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-parser": { @@ -10153,20 +10227,10 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } + "pikaday": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/pikaday/-/pikaday-1.8.2.tgz", + "integrity": "sha512-TNtsE+34BIax3WtkB/qqu5uepV1McKYEgvL3kWzU7aqPCpMEN6rBF3AOwu4WCwAealWlBGobXny/9kJb49C1ew==" }, "pirates": { "version": "4.0.1", @@ -10499,9 +10563,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10539,9 +10603,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10583,9 +10647,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10621,9 +10685,9 @@ }, "dependencies": { "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -11036,6 +11100,12 @@ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11144,13 +11214,13 @@ } }, "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" } }, "reduce-reducers": { @@ -11271,15 +11341,6 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -11442,6 +11503,15 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -11777,9 +11847,9 @@ } }, "sass-loader": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-11.1.1.tgz", - "integrity": "sha512-fOCp/zLmj1V1WHDZbUbPgrZhA7HKXHEqkslzB+05U5K9SbSbcmH91C7QLW31AsXikxUMaxXRhhcqWZAxUMLDyA==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.1.0.tgz", + "integrity": "sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==", "dev": true, "requires": { "klona": "^2.0.4", @@ -12567,12 +12637,12 @@ } }, "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { - "get-stdin": "^4.0.1" + "min-indent": "^1.0.0" } }, "strip-json-comments": { @@ -12787,6 +12857,11 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, "tar": { "version": "4.4.19", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", @@ -12873,9 +12948,9 @@ "dev": true }, "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -13160,9 +13235,9 @@ } }, "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, "true-case-path": { diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 5f03dee95d44..cfe6b4598d42 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -48,6 +48,7 @@ "path-parser": "^4.2.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", + "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^0.4.3", "uniqid": "^5.4.0", @@ -79,10 +80,10 @@ "file-loader": "^6.2.0", "jest": "^25.1.0", "mini-css-extract-plugin": "^1.6.2", - "node-sass": "^5.0.0", + "node-sass": "^6.0.1", "postcss": "^8.3.5", "postcss-loader": "^4.3.0", - "sass-loader": "^11.1.1", + "sass-loader": "12.1", "style-loader": "^1.3.0", "webpack": "^5.44.0", "webpack-cli": "^4.7.2" diff --git a/apps/block_scout_web/assets/static/images/spinner.svg b/apps/block_scout_web/assets/static/images/spinner.svg new file mode 100644 index 000000000000..dd29cb703c25 --- /dev/null +++ b/apps/block_scout_web/assets/static/images/spinner.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index a14c78a169c1..e7d43be8622f 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -98,7 +98,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', diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index 8e10f031a957..cfb9b0d17bf3 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -52,7 +52,10 @@ config :block_scout_web, dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"), circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"), test_tokens_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN"), - max_size_to_show_array_as_is: Integer.parse(System.get_env("MAX_SIZE_UNLESS_HIDE_ARRAY", "50")) + max_size_to_show_array_as_is: Integer.parse(System.get_env("MAX_SIZE_UNLESS_HIDE_ARRAY", "50")), + max_length_to_show_string_without_trimming: System.get_env("MAX_STRING_LENGTH_WITHOUT_TRIMMING", "2040"), + re_captcha_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY", nil), + re_captcha_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY", nil) config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 7adda14b274f..fa370b2895b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -218,8 +218,16 @@ defmodule BlockScoutWeb.Chain do [paging_options: %{@default_paging_options | key: {value, 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 + [paging_options: Map.put(paging_options, key, value)] + end + def param_to_block_number(formatted_number) when is_binary(formatted_number) do case Integer.parse(formatted_number) do {number, ""} -> {:ok, number} @@ -323,6 +331,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 diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index f84c72df9304..7dcd553c4137 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -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, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex index c2021cf26aee..ccb9c12fc2d7 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex @@ -7,7 +7,9 @@ defmodule BlockScoutWeb.AddressContractVerificationController do alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.SmartContract - alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion} + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} + alias Explorer.SmartContract.Solidity.PublisherWorker, as: SolidityPublisherWorker + alias Explorer.SmartContract.Vyper.PublisherWorker, as: VyperPublisherWorker alias Explorer.ThirdPartyIntegrations.Sourcify def new(conn, %{"address_id" => address_hash_string}) do @@ -26,7 +28,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do ) compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(:solc) do {:ok, compiler_versions} -> compiler_versions @@ -50,7 +52,18 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "external_libraries" => external_libraries } ) do - Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) + Que.add(SolidityPublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn}) + + send_resp(conn, 204, "") + end + + def create( + conn, + %{ + "smart_contract" => smart_contract + } + ) do + Que.add(VyperPublisherWorker, {smart_contract["address_hash"], smart_contract, conn}) send_resp(conn, 204, "") end @@ -104,7 +117,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do end def create(conn, _params) do - Que.add(PublisherWorker, {"", %{}, %{}, conn}) + Que.add(SolidityPublisherWorker, {"", %{}, %{}, conn}) send_resp(conn, 204, "") end @@ -155,14 +168,19 @@ defmodule BlockScoutWeb.AddressContractVerificationController do end defp process_metadata_and_publish(address_hash_string, verification_metadata, is_partial, conn \\ nil) do - %{"params_to_publish" => params_to_publish, "abi" => abi, "secondary_sources" => secondary_sources} = - parse_params_from_sourcify(address_hash_string, verification_metadata) + %{ + "params_to_publish" => params_to_publish, + "abi" => abi, + "secondary_sources" => secondary_sources, + "compilation_target_file_path" => compilation_target_file_path + } = parse_params_from_sourcify(address_hash_string, verification_metadata) ContractController.publish(conn, %{ "addressHash" => address_hash_string, "params" => Map.put(params_to_publish, "partially_verified", is_partial), "abi" => abi, - "secondarySources" => secondary_sources + "secondarySources" => secondary_sources, + "compilationTargetFilePath" => compilation_target_file_path }) end @@ -207,7 +225,8 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "params_to_publish" => extract_primary_source_code(content, Map.get(full_params_acc, "params_to_publish")), "abi" => Map.get(full_params_acc, "abi"), "secondary_sources" => Map.get(full_params_acc, "secondary_sources"), - "compilation_target_file_name" => Map.get(full_params_acc, "compilation_target_file_name") + "compilation_target_file_path" => Map.get(full_params_acc, "compilation_target_file_path"), + "compilation_target_file_name" => compilation_target_file_name } else secondary_sources = [ @@ -218,16 +237,26 @@ defmodule BlockScoutWeb.AddressContractVerificationController do "params_to_publish" => Map.get(full_params_acc, "params_to_publish"), "abi" => Map.get(full_params_acc, "abi"), "secondary_sources" => secondary_sources, - "compilation_target_file_name" => Map.get(full_params_acc, "compilation_target_file_name") + "compilation_target_file_path" => Map.get(full_params_acc, "compilation_target_file_path"), + "compilation_target_file_name" => compilation_target_file_name } end end) end - defp prepare_additional_source(address_hash_string, %{"name" => name, "content" => content, "path" => _path}) do + defp prepare_additional_source(address_hash_string, %{"name" => _name, "content" => content, "path" => path}) do + splitted_path = + path + |> String.split("/") + + trimmed_path = + splitted_path + |> Enum.slice(9..Enum.count(splitted_path)) + |> Enum.join("/") + %{ "address_hash" => address_hash_string, - "file_name" => name, + "file_name" => "/" <> trimmed_path, "contract_source_code" => content } end @@ -261,6 +290,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do %{ "params_to_publish" => params, "abi" => abi, + "compilation_target_file_path" => compilation_target_file_path, "compilation_target_file_name" => compilation_target_file_name, "secondary_sources" => [] } diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex index 2427d82aedbb..e36ef050f524 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_via_flattened_code_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do alias BlockScoutWeb.Controller alias Explorer.Chain alias Explorer.Chain.SmartContract - alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion} + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler, Solidity.PublisherWorker} def new(conn, %{"address_id" => address_hash_string}) do if Chain.smart_contract_fully_verified?(address_hash_string) do @@ -22,7 +22,7 @@ defmodule BlockScoutWeb.AddressContractVerificationViaFlattenedCodeController do ) compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(:solc) do {:ok, compiler_versions} -> compiler_versions diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex new file mode 100644 index 000000000000..f1cb3832e2e6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_vyper_controller.ex @@ -0,0 +1,47 @@ +defmodule BlockScoutWeb.AddressContractVerificationVyperController do + use BlockScoutWeb, :controller + + alias Explorer.Chain.SmartContract + alias Explorer.SmartContract.{CompilerVersion, Vyper.PublisherWorker} + + def new(conn, %{"address_id" => address_hash_string}) do + changeset = + SmartContract.changeset( + %SmartContract{address_hash: address_hash_string}, + %{} + ) + + compiler_versions = + case CompilerVersion.fetch_versions(:vyper) do + {:ok, compiler_versions} -> + compiler_versions + + {:error, _} -> + [] + end + + render(conn, "new.html", + changeset: changeset, + compiler_versions: compiler_versions, + address_hash: address_hash_string + ) + end + + def create( + conn, + %{ + "smart_contract" => smart_contract + } + ) do + Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, conn}) + + send_resp(conn, 204, "") + end + + def parse_optimization_runs(%{"runs" => runs}) do + case Integer.parse(runs) do + {integer, ""} -> integer + _ -> 200 + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex index 9bfe2b5b0199..c8e90a2b653f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -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 - def 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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex index 11e148a39081..fd59482ad1c8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_balance_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex index a9f017252a86..96ab130d56f0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -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 ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index a5cfdafd5a3c..f0dd1e12184f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -9,6 +9,14 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.{AccessHelpers, Controller, TransactionView} alias Explorer.{Chain, Market} + + alias Explorer.Chain.{ + AddressInternalTransactionCsvExporter, + AddressLogCsvExporter, + AddressTokenTransferCsvExporter, + AddressTransactionCsvExporter + } + alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -139,4 +147,105 @@ defmodule BlockScoutWeb.AddressTransactionController do end end end + + def token_transfers_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period + }) + when is_binary(address_hash_string) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressTokenTransferCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=token_transfers.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def token_transfers_csv(conn, _), do: not_found(conn) + + def transactions_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period + }) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressTransactionCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=transactions.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def transactions_csv(conn, _), do: not_found(conn) + + def internal_transactions_csv(conn, %{ + "address_id" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period + }) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressInternalTransactionCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=internal_transactions.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def internal_transactions_csv(conn, _), do: not_found(conn) + + def logs_csv(conn, %{"address_id" => address_hash_string, "from_period" => from_period, "to_period" => to_period}) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + {:ok, address} <- Chain.hash_to_address(address_hash) do + address + |> AddressLogCsvExporter.export(from_period, to_period) + |> Enum.into( + conn + |> put_resp_content_type("application/csv") + |> put_resp_header("content-disposition", "attachment; filename=logs.csv") + |> send_chunked(200) + ) + else + :error -> + unprocessable_entity(conn) + + {:error, :not_found} -> + not_found(conn) + end + end + + def logs_csv(conn, _), do: not_found(conn) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index 5d3662ce759c..d1d6f9f7694c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -8,7 +8,8 @@ defmodule BlockScoutWeb.API.RPC.ContractController do alias Explorer.Chain alias Explorer.Chain.Events.Publisher, as: EventsPublisher alias Explorer.Chain.{Hash, SmartContract} - alias Explorer.SmartContract.Publisher + alias Explorer.SmartContract.Solidity.Publisher + alias Explorer.SmartContract.Vyper.Publisher, as: VyperPublisher alias Explorer.ThirdPartyIntegrations.Sourcify def verify(conn, %{"addressHash" => address_hash} = params) do @@ -198,24 +199,58 @@ defmodule BlockScoutWeb.API.RPC.ContractController do end end - def publish_without_broadcast(%{"addressHash" => address_hash, "params" => params, "abi" => abi} = input) do - params = - if Map.has_key?(input, "secondarySources") do - params - |> Map.put("secondary_sources", Map.get(input, "secondarySources")) - else - params - end + def verify_vyper_contract(conn, %{"addressHash" => address_hash} = params) do + with {:params, {:ok, fetched_params}} <- {:params, fetch_vyper_verify_params(params)}, + {:format, {:ok, casted_address_hash}} <- to_address_hash(address_hash), + {:publish, {:ok, _}} <- + {:publish, VyperPublisher.publish(address_hash, fetched_params)} do + address = Chain.address_hash_to_address_with_source_code(casted_address_hash) - case Publisher.publish_smart_contract(address_hash, params, abi) do - {:ok, _contract} = result -> - result + render(conn, :verify, %{contract: address}) + else + {:publish, + {:error, + %Ecto.Changeset{ + errors: [ + address_hash: + {"has already been taken", + [ + constraint: :unique, + constraint_name: "smart_contracts_address_hash_index" + ]} + ] + }}} -> + render(conn, :error, error: "Smart-contract already verified.") - {:error, changeset} -> - {:error, changeset} + {:publish, _} -> + render(conn, :error, error: "Something went wrong while publishing the contract.") + + {:format, :error} -> + render(conn, :error, error: "Invalid address hash") + + {:params, {:error, error}} -> + render(conn, :error, error: error) end end + def publish_without_broadcast( + %{"addressHash" => address_hash, "abi" => abi, "compilationTargetFilePath" => file_path} = input + ) do + params = proccess_params(input) + + address_hash + |> Publisher.publish_smart_contract(params, abi, file_path) + |> proccess_response() + end + + def publish_without_broadcast(%{"addressHash" => address_hash, "abi" => abi} = input) do + params = proccess_params(input) + + address_hash + |> Publisher.publish_smart_contract(params, abi) + |> proccess_response() + end + def publish(nil, %{"addressHash" => _address_hash} = input) do publish_without_broadcast(input) end @@ -226,6 +261,25 @@ defmodule BlockScoutWeb.API.RPC.ContractController do EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand) end + def proccess_params(input) do + if Map.has_key?(input, "secondarySources") do + input["params"] + |> Map.put("secondary_sources", Map.get(input, "secondarySources")) + else + input["params"] + end + end + + def proccess_response(response) do + case response do + {:ok, _contract} = result -> + result + + {:error, changeset} -> + {:error, changeset} + end + end + def listcontracts(conn, params) do with pagination_options <- Helpers.put_pagination_options(%{}, params), {:params, {:ok, options}} <- {:params, add_filters(pagination_options, params)} do @@ -422,6 +476,15 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> parse_optimization_runs() end + defp fetch_vyper_verify_params(params) do + {:ok, %{}} + |> required_param(params, "addressHash", "address_hash") + |> required_param(params, "name", "name") + |> required_param(params, "compilerVersion", "compiler_version") + |> required_param(params, "contractSourceCode", "contract_source_code") + |> optional_param(params, "constructorArguments", "constructor_arguments") + end + defp parse_optimization_runs({:ok, %{"optimization_runs" => runs} = opts}) when is_bitstring(runs) do {:ok, Map.put(opts, "optimization_runs", 200)} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex index 07b17141413a..59431742ad87 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do alias Explorer.Chain alias Explorer.Chain.Hash.Address - alias Explorer.SmartContract.Publisher + alias Explorer.SmartContract.Solidity.Publisher def create(conn, params) do with {:ok, hash} <- validate_address_hash(params["address_hash"]), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex index 445b70b5c2cb..6c09c9361de0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_controller.ex @@ -51,7 +51,7 @@ defmodule BlockScoutWeb.BlockController do |> Map.delete("type") |> Map.put("block_type", block_type) - block_path( + blocks_path( conn, :index, params_with_block_type diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex index db99f5ae6453..8de421098f9b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex @@ -2,7 +2,7 @@ defmodule BlockScoutWeb.BlockTransactionController do use BlockScoutWeb, :controller import BlockScoutWeb.Chain, - only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + only: [paging_options: 1, put_key_value_to_paging_options: 3, next_page_params: 3, split_list_by_page: 1] import Explorer.Chain, only: [hash_to_block: 2, number_to_block: 2, string_to_block_hash: 1] @@ -26,7 +26,7 @@ defmodule BlockScoutWeb.BlockTransactionController do [to_address: :names] => :optional } ], - paging_options(params) + put_key_value_to_paging_options(paging_options(params), :is_index_in_asc_order, true) ) transactions_plus_one = Chain.block_to_transactions(block.hash, full_options) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex new file mode 100644 index 000000000000..ad170262a6e9 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/captcha_cotroller.ex @@ -0,0 +1,16 @@ +defmodule BlockScoutWeb.CaptchaController do + use BlockScoutWeb, :controller + + alias Plug.Conn + + def index(conn, %{"captchaResponse" => captcha_response, "type" => "JSON"}) do + body = "secret=#{Application.get_env(:block_scout_web, :re_captcha_secret_key)}&response=#{captcha_response}" + + headers = [{"Content-type", "application/x-www-form-urlencoded"}] + + case HTTPoison.post("https://www.google.com/recaptcha/api/siteverify", body, headers, []) do + {:ok, %HTTPoison.Response{status_code: status_code, body: body}} -> + Conn.resp(conn, status_code, body) + end + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex new file mode 100644 index 000000000000..8cab1d24058b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/csv_export_controller.ex @@ -0,0 +1,35 @@ +defmodule BlockScoutWeb.CsvExportController do + use BlockScoutWeb, :controller + + alias BlockScoutWeb.AccessHelpers + alias Explorer.Chain + + def index(conn, %{"address" => address_hash_string, "type" => type} = params) do + with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), + :ok <- Chain.check_address_exists(address_hash), + {:ok, false} <- AccessHelpers.restricted_access?(address_hash_string, params), + true <- supported_export_type(type) do + render(conn, "index.html", address_hash_string: address_hash_string, type: type) + else + _ -> + not_found(conn) + end + end + + def index(conn, _params) do + not_found(conn) + end + + defp supported_export_type(type) do + Enum.member?(supported_types(), type) + end + + defp supported_types do + [ + "internal-transactions", + "transactions", + "token-transfers", + "logs" + ] + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index d6b198fbdda9..7ea40921089c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -42,6 +42,17 @@ defmodule BlockScoutWeb.SmartContractController do end end + read_functions_required_wallet = + if action == "read" do + if contract_type == "proxy" do + Reader.read_functions_required_wallet_proxy(implementation_address_hash_string) + else + Reader.read_functions_required_wallet(address_hash) + end + else + [] + end + contract_abi = Poison.encode!(address.smart_contract.abi) implementation_abi = @@ -58,6 +69,7 @@ defmodule BlockScoutWeb.SmartContractController do |> put_layout(false) |> render( "_functions.html", + read_functions_required_wallet: read_functions_required_wallet, read_only_functions: functions, address: address, contract_abi: contract_abi, @@ -93,16 +105,26 @@ defmodule BlockScoutWeb.SmartContractController do with true <- ajax?(conn), {:ok, address_hash} <- Chain.string_to_address_hash(params["id"]), - {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do - contract_type = if Chain.proxy_contract?(address.hash, address.smart_contract.abi), do: :proxy, else: :regular + {:ok, _address} <- Chain.find_contract_address(address_hash, address_options, true) do + contract_type = if params["type"] == "proxy", do: :proxy, else: :regular %{output: outputs, names: names} = - Reader.query_function_with_names( - address_hash, - %{method_id: params["method_id"], args: params["args"]}, - contract_type, - params["function_name"] - ) + if params["from"] do + Reader.query_function_with_names( + address_hash, + %{method_id: params["method_id"], args: params["args"]}, + contract_type, + params["function_name"], + params["from"] + ) + else + Reader.query_function_with_names( + address_hash, + %{method_id: params["method_id"], args: params["args"]}, + contract_type, + params["function_name"] + ) + end conn |> put_status(200) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex index 1c5b9651e26e..c7b12173ae38 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex @@ -35,7 +35,8 @@ defmodule BlockScoutWeb.Tokens.HolderController do View.render_to_string(HolderView, "_token_balances.html", address_hash: address_hash, token_balance: token_balance, - token: token + token: token, + conn: conn ) end) diff --git a/apps/block_scout_web/lib/block_scout_web/csp_header.ex b/apps/block_scout_web/lib/block_scout_web/csp_header.ex index f393323efa4e..824291b42414 100644 --- a/apps/block_scout_web/lib/block_scout_web/csp_header.ex +++ b/apps/block_scout_web/lib/block_scout_web/csp_header.ex @@ -13,12 +13,12 @@ defmodule BlockScoutWeb.CSPHeader do "content-security-policy" => "\ connect-src 'self' #{websocket_endpoints(conn)} https://request-global.czilladx.com/ https://raw.githubusercontent.com/trustwallet/assets/;\ default-src 'self';\ - script-src 'self' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' https://cdn.segment.com https://api.segment.io https://coinzillatag.com;\ + script-src 'self' 'unsafe-inline' 'unsafe-eval' https://coinzillatag.com https://www.google.com https://www.gstatic.com;\ style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\ img-src 'self' * data:;\ media-src 'self' * data:;\ font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\ - frame-src 'self' 'unsafe-inline' 'unsafe-eval' https://request-global.czilladx.com/;\ + frame-src 'self' 'unsafe-inline' 'unsafe-eval' https://request-global.czilladx.com/ https://www.google.com;\ " }) end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index f7aff41f503c..f67c3f09e534 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -514,7 +514,8 @@ defmodule BlockScoutWeb.Etherscan do """, "ContractName" => "Test", "CompilerVersion" => "v0.2.1-2016-01-30-91a6b35", - "OptimizationUsed" => "1" + "OptimizationUsed" => "1", + "FileName" => "{sourcify path or empty}" } } @@ -2562,6 +2563,76 @@ defmodule BlockScoutWeb.Etherscan do ] } + @contract_verify_vyper_contract_action %{ + name: "verify_vyper_contract", + description: """ + Verify a vyper contract with its source code and contract creation information. +
+
+

curl POST example:

+
+
+
+
+
+ curl --location --request POST 'http://localhost:4000/api?module=contract&action=verify_vyper_contract' \ + --form 'contractSourceCode="SOURCE_CODE"' \ + --form 'name="Vyper_contract"' \ + --form 'addressHash="0xE60B1B8bD493569a3E945be50A6c89d29a560Fa1"' \ + --form 'compilerVersion="v0.2.12"' + +
+
+
+ """, + required_params: [ + %{ + key: "addressHash", + placeholder: "addressHash", + type: "string", + description: "The address of the contract." + }, + %{ + key: "name", + placeholder: "name", + type: "string", + description: "The name of the contract." + }, + %{ + key: "compilerVersion", + placeholder: "compilerVersion", + type: "string", + description: "The compiler version for the contract." + }, + %{ + key: "contractSourceCode", + placeholder: "contractSourceCode", + type: "string", + description: "The source code of the contract." + } + ], + optional_params: [ + %{ + key: "constructorArguments", + type: "string", + description: "The constructor argument data provided." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@contract_verify_example_value), + type: "model", + model: @contract_model + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@contract_verify_example_value_error) + } + ] + } @contract_getabi_action %{ name: "getabi", description: "Get ABI for verified contract. Also available through a GraphQL 'addresses' query.", @@ -2816,7 +2887,8 @@ defmodule BlockScoutWeb.Etherscan do @contract_getabi_action, @contract_getsourcecode_action, @contract_verify_action, - @contract_verify_via_sourcify_action + @contract_verify_via_sourcify_action, + @contract_verify_vyper_contract_action ] } diff --git a/apps/block_scout_web/lib/block_scout_web/gettext.ex b/apps/block_scout_web/lib/block_scout_web/gettext.ex index 9b8a7833f2c7..3d54e804bf98 100644 --- a/apps/block_scout_web/lib/block_scout_web/gettext.ex +++ b/apps/block_scout_web/lib/block_scout_web/gettext.ex @@ -21,11 +21,4 @@ defmodule BlockScoutWeb.Gettext do See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. """ use Gettext, otp_app: :block_scout_web - - @dialyzer [ - {:nowarn_function, "MACRO-dgettext": 3}, - {:nowarn_function, "MACRO-dgettext": 4}, - {:nowarn_function, "MACRO-dngettext": 5}, - {:nowarn_function, "MACRO-dngettext": 6} - ] end diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index 2300ca8354c0..dcaf1764a005 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.Notifier do alias BlockScoutWeb.{ AddressContractVerificationViaFlattenedCodeView, AddressContractVerificationViaJsonView, + AddressContractVerificationVyperView, Endpoint } @@ -17,7 +18,7 @@ defmodule BlockScoutWeb.Notifier do alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token - alias Explorer.SmartContract.{Solidity.CodeCompiler, Solidity.CompilerVersion} + alias Explorer.SmartContract.{CompilerVersion, Solidity.CodeCompiler} alias Phoenix.View def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do @@ -47,6 +48,8 @@ defmodule BlockScoutWeb.Notifier do {:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}} ) do verification_from_json_upload? = Map.has_key?(conn.params, "file") + verification_from_flattened_source? = Map.has_key?(conn.params, "external_libraries") + compiler = if verification_from_flattened_source?, do: :solc, else: :vyper contract_verification_result = case contract_verification_result do @@ -55,7 +58,7 @@ defmodule BlockScoutWeb.Notifier do {:error, changeset} -> compiler_versions = - case CompilerVersion.fetch_versions() do + case CompilerVersion.fetch_versions(compiler) do {:ok, compiler_versions} -> compiler_versions @@ -64,10 +67,10 @@ defmodule BlockScoutWeb.Notifier do end view = - if verification_from_json_upload? do - AddressContractVerificationViaJsonView - else - AddressContractVerificationViaFlattenedCodeView + cond do + verification_from_json_upload? -> AddressContractVerificationViaJsonView + verification_from_flattened_source? -> AddressContractVerificationViaFlattenedCodeView + true -> AddressContractVerificationVyperView end result = diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_dropdown.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_dropdown.html.eex new file mode 100644 index 000000000000..a6dbb173fa01 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_dropdown.html.eex @@ -0,0 +1,16 @@ +
+ + + + + + <%= gettext("Fetching tokens...") %> + + +
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex index 8a41882b5df8..64566f36796a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_link.html.eex @@ -9,7 +9,7 @@ <% 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 %> + <%= render BlockScoutWeb.AddressView, "_responsive_hash.html", address: @address, contract: @contract, truncate: assigns[:truncate], use_custom_tooltip: @use_custom_tooltip, no_tooltip: assigns[:no_tooltip] %> <% end %> <% end %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex index 97a528f2a223..6444bac068d8 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_responsive_hash.html.eex @@ -1,14 +1,24 @@ <%= if name = primary_name(@address) do %> - <%= if @use_custom_tooltip == true do %> - <%= name %> (<%= short_hash(@address) %>...) - <% else %> - + <%= if assigns[:no_tooltip] do %> + <%= if @use_custom_tooltip == true do %> + <%= name %> (<%= short_hash(@address) %>...) + <% else %> <%= short_contract_name(name, 30) %> <%= short_contract_name(name, 10) %> (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>) - - <% end %> + <% end %> + <% else %> + <%= if @use_custom_tooltip == true do %> + <%= name %> (<%= short_hash(@address) %>...) + <% else %> + + <%= short_contract_name(name, 30) %> + <%= short_contract_name(name, 10) %> + (<%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %>) + + <% end %> + <% end %> <% else %> <%= if assigns[:truncate] do %> <%= BlockScoutWeb.AddressView.trimmed_hash(@address.hash) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex index 81f8a182ad39..9e91a5a75e74 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex @@ -13,7 +13,7 @@ + <%= if !empty_exchange_rate?(@exchange_rate) do %> data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex index 39a7dbf2facb..37c81aca4001 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex @@ -2,7 +2,7 @@ <%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %>
-

<%= gettext "Addresses" %>

+

<%= gettext "CELO" %> <%= gettext "Addresses" %>

<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> @@ -14,36 +14,37 @@ -
-   -
+
+   +
-
- Address -
+
+ Address +
-
- Balance -
+
+ Balance +
- <%= if balance_percentage_enabled?(@total_supply) do %> - -
- Percentage -
- - <% end %> + <%= if balance_percentage_enabled?(@total_supply) do %> + +
+ Percentage +
+ + <% end %> -
- Txn Count -
+
+ Txn Count +
- <%= 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 %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex index b5abe71701b5..711e8c309e29 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -5,11 +5,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 %> -
+
-
+
-
+
<%= 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)" %> @@ -26,7 +26,7 @@ <% true -> %> <%= nil %> <% end %> -

+

<%= address_title(@address) %> <%= gettext "Details" %>
@@ -58,85 +58,221 @@ <% end %>

-

<%= @address %>

- <%= if @is_proxy do %> - <% implementation_address = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || @burn_address %> -
- <%= gettext("Implementation:") %> - <%= link( - implementation_address, - to: address_path(@conn, :show, implementation_address), - class: "contract-address mb-2", - style: "margin-top: 0.5rem;" - ) - %> - <% end %> -
- <%= if address_name = primary_name(@address) do %> - - <%= short_contract_name(address_name, 30) %> - - <% end %> - <%= if @address.token do %> - - <%= link(token_title(@address.token), to: token_path(@conn, :show, @address.hash), "data-test": "token_hash_link" ) %> - - <% end %> - - - - <%= if @address.fetched_coin_balance_block_number do %> - - <%= gettext("Last Balance Update: Block #") %><%= @address.fetched_coin_balance_block_number %> - +

<%= @address %>

+ + + <% address_name = primary_name(@address) %> + <%= cond do %> + <% @address.token -> %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Token name and symbol.") %> + <%= gettext("Token") %> +
+
+ <%= link( + token_title(@address.token), + to: token_path(@conn, + :show, + @address.hash), + "data-test": + "token_hash_link" + ) + %> +
+
+ <% address_name -> %> + <%= if contract?(@address) do %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The name found in the source code of the Contract.") %> + <%= gettext("Contract Name") %> +
+
+ <%= short_contract_name(address_name, 30) %> +
+
+ <% else %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("The name of the validator.") %> + <%= gettext("Validator Name") %> +
+
+ <%= short_contract_name(address_name, 30) %> +
+
<% end %> - - - - - -
-
+ <% true -> %> + <% end %> + <% from_address_hash = from_address_hash(@address) %> - <%= if contract?(@address) do %> - - <%= if from_address_hash do %> - <%= gettext "Created by" %> <%= link( - trimmed_hash(from_address_hash(@address)), - to: address_path(@conn, :show, from_address_hash(@address)) - ) %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Transactions and address of creation.") %> + <%= gettext("Creator") %> +
+
+ <%= if from_address_hash do %> + <%= link( + trimmed_hash(from_address_hash(@address)), + to: address_path(@conn, :show, from_address_hash(@address)) + ) %> - <%= gettext "at" %> + <%= gettext "at" %> + <%= link( + trimmed_hash(transaction_hash(@address)), + to: transaction_path(@conn, :show, transaction_hash(@address)), + "data-test": "transaction_hash_link" + ) %> + <% else %> + + <% end %> +
+
+ <% end %> + + <%= if @is_proxy do %> + <% implementation_address = Chain.get_implementation_address_hash(@address.hash, @address.smart_contract.abi) || "0x0000000000000000000000000000000000000000" %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Implementation address of the proxy contract.") %> + <%= gettext("Implementation") %> +
+
<%= link( - trimmed_hash(transaction_hash(@address)), - to: transaction_path(@conn, :show, transaction_hash(@address)), - "data-test": "transaction_hash_link" - ) %> + implementation_address, + to: address_path(@conn, :show, implementation_address), + class: "contract-address mb-2", + style: "margin-top: 0.5rem;" + ) + %> +
+
+ <% end %> + +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens).") %> + <%= gettext("Balance") %> +
+
+ <%= balance(@address) %> + <%= if !match?({:pending, _}, @coin_balance_status) && !empty_exchange_rate?(@exchange_rate) do %> + <% usd_value = to_string(@exchange_rate.usd_value) %> + + ( + ) + + <% end %> +
+
+ +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("All tokens in the account and total value.") %> + <%= gettext("Tokens") %> +
+
+ <%= render BlockScoutWeb.AddressView, "_balance_dropdown.html", conn: @conn, address: @address %> +
+
+ +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of transactions related to this address.") %> + <%= gettext("Transactions") %> +
+
+ <%= if @conn.request_path |> String.contains?("/transactions") do %> + + <% else %> - + + <% end %> - +
+
+ +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Number of transfers to/from this address.") %> + <%= gettext("Transfers") %> +
+
+ <%= if @conn.request_path |> String.contains?("/token-transfers") do %> + + + <% else %> + + + <% end %> +
+
+ +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Gas used by the address.") %> + <%= gettext("Gas Used") %> +
+
+ + +
+
+ + <%= if @address.fetched_coin_balance_block_number do %> +
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", + text: gettext("Block number in which the address was updated.") %> + <%= gettext("Last Balance Update") %> +
+
+ <%= link( + @address.fetched_coin_balance_block_number, + to: block_path(@conn, :show, @address.fetched_coin_balance_block_number), + class: "tile-title-lg" + ) %> +
+
<% end %> - - + +
- -
- <%= render BlockScoutWeb.AddressView, "_balance_card.html", conn: @conn, address: @address, exchange_rate: @exchange_rate, coin_balance_status: @coin_balance_status %> -
-
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index f209ae540f00..66de7bfa3229 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -51,7 +51,7 @@




<%= gettext "Optimization enabled" %>
-
<%= format_optimization_text(target_contract.optimization) %>
+
<%= if target_contract.is_vyper_contract, do: "N/A", else: format_optimization_text(target_contract.optimization) %>
<%= if @is_proxy do %>
@@ -101,7 +101,7 @@ <% end %>
-

<%= gettext "Contract source code" %>

+

<%= target_contract.file_path || gettext "Contract source code" %>

diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex index 51795dfb93c8..626058e18c7e 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex @@ -26,18 +26,25 @@
- <%= label f, "Verification via" %> + <%= label f, "Verify" %>
<%= radio_button f, :verify_via, true, checked: true, class: "form-check-input verify-via-flattened-code", "aria-describedby": "verify_via-help-block" %>
- <%= label :verify_via, :true, gettext("Flattened source code"), class: "radio-text" %> + <%= label :verify_via, :true, gettext("Via flattened source code"), class: "radio-text" %>
+ <%= if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do %> +
+ <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-sourcify" %> +
+ <%= label :verify_via, :false, gettext("Via Sourcify: Sources and metadata JSON file"), class: "radio-text" %> +
+ <% end %>
- <%= radio_button f, :verify_via, false, class: "form-check-input verify-via-sourcify" %> + <%= radio_button f, :verify_via, false, class: "form-check-input verify-vyper-contract" %>
- <%= label :verify_via, :false, gettext("Sourcify: Sources and metadata JSON file"), class: "radio-text" %> + <%= label :verify_via, :false, gettext("Vyper contract"), class: "radio-text" %>
<%= error_tag f, :verify_via, id: "verify_via-help-block", class: "text-danger form-error" %> @@ -52,7 +59,9 @@
2. Verification through Sourcify.
a) if smart-contract already verified on Sourcify, it will automatically fetch the data from the repo
- b) otherwise you will be asked to upload source files and JSON metadata file(s).
+ b) otherwise you will be asked to upload source files and JSON metadata file(s).
+ 3. Verification of Vyper contract. +
@@ -86,6 +95,14 @@ style: "display: none;", "data-button-loading": "animation" ) %> + <%= link( + gettext("Next"), + to: address_verify_vyper_contract_path(@conn, :new, @address_hash), + id: "verify_vyper_contract_button", + class: "btn-full-primary mr-2", + style: "display: none;", + "data-button-loading": "animation" + ) %> <%= link( gettext("Cancel"), diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex index dfa2f7e91bab..501729afabae 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex @@ -15,7 +15,7 @@
-

<%= gettext "New Smart Contract Verification" %>

+

<%= gettext "New Solidity Smart Contract Verification" %>

<%= form_for @changeset, address_contract_verification_path(@conn, :create), diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex new file mode 100644 index 000000000000..d4624055c0a5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex @@ -0,0 +1,102 @@ +<% metadata_for_verification = Chain.get_address_verified_twin_contract(@address_hash).verified_contract %> +<% contract_name_value = if metadata_for_verification, do: metadata_for_verification.name, else: "Vyper_contract" %> +<% compiler_version = if metadata_for_verification, do: metadata_for_verification.compiler_version, else: "latest" %> +<% contract_source_code_value = if metadata_for_verification, do: metadata_for_verification.contract_source_code, else: "" %> +
+ + +
+

<%= gettext "New Vyper Smart Contract Verification" %>

+ + <%= form_for @changeset, + address_contract_verification_path(@conn, :create), + [], + fn f -> %> + +
+
+ <%= label f, :address_hash, gettext("Contract Address") %> +
+ <%= text_input f, :address_hash, class: "form-control border-rounded", "aria-describedby": "contract-address-help-block", readonly: true %> + <%= error_tag f, :address_hash, id: "contract-address-help-block", class: "text-danger form-error" %> +
+
The 0x address supplied on contract creation.
+
+
+ +
+
+ <%= label f, :name, gettext("Contract Name") %> +
+ <%= text_input f, :name, class: "form-control border-rounded", "aria-describedby": "contract-name-help-block", "data-test": "contract_name", value: contract_name_value, disabled: "true" %> + <%= error_tag f, :name, id: "contract-name-help-block", class: "text-danger form-error" %> +
+
Must match the name specified in the code.
+
+
+ +
+
+ <%= label f, :compiler_version, gettext("Compiler") %> +
+ <%= select f, :compiler_version, @compiler_versions, class: "form-control border-rounded", selected: compiler_version, "aria-describedby": "compiler-help-block" %> + <%= error_tag f, :compiler_version, id: "compiler-help-block", class: "text-danger form-error" %> +
+
+
+
+ +
+
+ <%= label f, :contract_source_code, gettext("Enter the Vyper Contract Code") %> +
+ <%= textarea f, :contract_source_code, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-source-code-help-block", value: contract_source_code_value %> + <%= error_tag f, :contract_source_code, id: "contract-source-code-help-block", class: "text-danger form-error", "data-test": "contract-source-code-error" %> +
+
+
+
+ +
+
+ <%= label f, :constructor_arguments, gettext("ABI-encoded Constructor Arguments (if required by the contract)") %> +
+ <%= textarea f, :constructor_arguments, class: "form-control border-rounded monospace", rows: 3, "aria-describedby": "contract-constructor-arguments-help-block" %> + <%= error_tag f, :constructor_arguments, id: "contract-constructor-arguments-help-block", class: "text-danger form-error", "data-test": "contract-constructor-arguments-error" %> +
+
Add arguments in ABI hex encoded form. Constructor arguments are written right to left, and will be found at the end of the input created bytecode. They may also be parsed here.
+
+
+ +
+ + <%= submit gettext("Verify & publish"), class: "btn-full-primary mr-2", "data-button-loading": "animation" %> + <%= reset gettext("Reset"), class: "btn-line mr-2 js-smart-contract-form-reset" %> + <%= + link( + gettext("Cancel"), + class: "btn-no-border", + to: address_contract_path(@conn, :index, @address_hash) + ) + %> +
+ <% end %> +
+ +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex index 7e902276d9c4..212f6a1a8093 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex @@ -63,7 +63,10 @@ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
- <%= 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 %> +
+ + <%= 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 %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex index deb221760116..96d486c65650 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex @@ -6,13 +6,7 @@ <%= gettext "To see accurate decoded input data, the contract must be verified." %> <%= case @log.transaction do %> <% %{to_address: %{hash: hash}} -> %> - <% path = - if Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:enabled] do - address_verify_contract_path(@conn, :new, hash) - else - address_verify_contract_via_flattened_code_path(@conn, :new, hash) - end - %> + <% path = address_verify_contract_path(@conn, :new, hash) %> <%= gettext "Verify the contract " %><%= gettext "here" %> <% _ -> %> <%= nil %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex index dd18d1ee87d1..2c731e987164 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex @@ -33,7 +33,10 @@ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
- <%= 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 %> +
+ + <%= 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 %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex index bd7ccc68adfa..5725de25a230 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/_tokens.html.eex @@ -1,22 +1,69 @@ -
-
-
- <%= 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 %> - <%= Gettext.gettext(BlockScoutWeb.Gettext, token_name(@token)) %> + + + + + + + + + <%= 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 %> + <% end %> - <%= @token.type %> - <%= @token.contract_address_hash %> - <%= if @token.usd_value do %> - + <% end %> + + <%= 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 %> + + + <%= @token.type %> + + + <%= format_according_to_decimals(@token_balance.value, @token.decimals) %> + + + <%= @token.symbol %> + + +

+ <% token_price = if @token_balance.token.usd_value, do: @token_balance.token.usd_value, else: nil %> + <%= ChainView.format_currency_value(token_price, "@") %> +

+ + + <%= 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) %> +

+ <%= ChainView.format_usd_value(lp_token_balance_usd) %> +

+ <% else %> + <%= if @token_balance.token.usd_value do %> +

+ <%= ChainView.format_usd_value(Chain.balance_in_usd(@token_balance)) %> +

<% end %> -
-
- - <%= format_according_to_decimals(@token.balance, @token.decimals) %> <%= Gettext.gettext(BlockScoutWeb.Gettext, @token.symbol) %> - -
-
-
+ <% end %> + + + <%= 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 + %> + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index 7d6d093c7249..9b35fb6e247a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -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 %> -
+
<%= render BlockScoutWeb.AddressView, "_tabs.html", address: @address, is_proxy: is_proxy, conn: @conn %>
-

<%= gettext "Tokens" %>

+ <%= render BlockScoutWeb.AddressTokenView, + "overview.html", + address: @address, + exchange_rate: @exchange_rate + %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
+
+ + + + + + + + + + + + + + + <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html", columns_num: 8 %> + +
+
 
+
+
Asset
+
+
Type
+
+
Amount
+
+
Symbol
+
+
Price
+
+
Value
+
+
Contract Address
+
+
+ @@ -23,13 +63,7 @@
-
- <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %> -
- -
- <%= 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 %> -
+ <%= 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 %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex new file mode 100644 index 000000000000..659323e53d43 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview.html.eex @@ -0,0 +1,64 @@ +<% native_coin = gettext("CELO") %> + +<% 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 + " + " + end +%> +<% native_coin_balance = + if native_coin_balance_usd do + native_coin_balance_usd <> " | " <> native_coin_balance_token + else + native_coin_balance_token + end +%> +
+ <%= 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"] + %> +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex new file mode 100644 index 000000000000..e104e87413ff --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/overview_item.html.eex @@ -0,0 +1,14 @@ +
+
+
+

<%= @title %>

+ <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip.html", + text: @tooltip, + additional_classes: ["ml-2"] + %> +
+
""> + <%= @value %> +
+
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex index 798bdc514dbd..502df9804958 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex @@ -1,57 +1,72 @@ -<%= if Decimal.cmp(@circles_total_balance, 0) == :gt do %> -

<%= format_according_to_decimals(@circles_total_balance, Decimal.new(18)) %> CRC

-<% end %> -<%= if Enum.any?(@token_balances) do %> -
-<% else %> - <%= tokens_count_title(@token_balances) %> -<% end %> - -